Java多线程
Posted yzg-14
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。
1. 串行并行并发
2. 进程
3. 线程
4. 创建线程的方式
A:创建线程并启动:继承Thread类
step1:创建子类,继承Thread类。 step2:重写run(),线程体。并发执行的内容,写在这个方法中。 step3:启动线程:start() /* class Cat class Person class MyException extends Exception{//异常类 } class MyThread extends Thread{//线程类 } */
package com.qf.demo02; //step1:创建子类,继承Thread类。 //step2:重写run(),线程体。并发执行的内容,写在这个方法中。 //step3:启动线程:start() //1.子类继承Thread类 class MyThreadA extends Thread{ @Override public void run() { //2.此处写要被并发执行的代码。。 for(int i =0;i<100;i++){ System.out.println(" t1..跳舞ing。。"+i); } } } class MyThreadB extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("t2..唱歌ing。。"+i); } } } public class Test1Thread { public static void main(String[] args) { //需求:并发实现边唱边跳。。 //创建线程的对象 MyThreadA t1 = new MyThreadA(); MyThreadB t2 = new MyThreadB(); //对象访问方法,直接执行方法中内容。不是并发的范畴 // t1.run(); // t2.run(); // for(int i = 0;i<100;i++){ // System.out.println(" main...伴舞"+i); // } //3.启动线程?表示该线程一切准备就绪,随时可以被cpu执行了。但是cpu是否来执行你,不确定。。 t1.start();//表示启动t1线程 t2.start();//表示启动t2线程 MyThreadA t3 = new MyThreadA(); t3.start();//老子想重来一次。。可否啊?java.lang.IllegalThreadStateException } }
B:实现Runnable接口,创建线程的方式二
step1:创建实现类,实现Runnable接口 step2:重写run(),线程体。并发执行的内容,写在这个方法中。 step3:启动线程: 创建实现类的对象:mt 根据实现类对象mt,创建Thread类对象t3,t4 start()启动该线程:t3,t4
对比run()和start() run(),是线程体。就是要并发执行内容。 start(),启动一个线程?就是该线程准备就绪了,随时可以被CPU执行。什么时候执行,CPU自己说了算。 一个线程,只能被start一次。
Thread类的构造方法 new Thread();//并发执行,执行run()方法。 new Thread(Runnable target);//并发执行,执行的run是Runnable接口的实现类实现run方法
package com.qf.demo02; /* step1:创建实现类,实现Runnable接口 step2:重写run(),线程体。并发执行的内容,写在这个方法中。 step3:启动线程: */ class MyThreadC implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(" 左手画圈。。。"+i); } } } class MyThreadD implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("右手画方块。。"+i); } } } public class Test2Runnable { public static void main(String[] args) { MyThreadC mt1 = new MyThreadC(); MyThreadD mt2 = new MyThreadD(); Thread t3 = new Thread(mt1);//当启动t3线程,执行mt1对应的run方法 Thread t4 = new Thread(mt2);//当启动t4线程,执行mt2对应的run方法 t3.start(); t4.start(); } }
两种启动方式比较
Thread类:JDK提供好的类,用于表示一个线程对象。实现类Runnable接口
run(),start()....
Runnable接口:定义了唯一的一个方法:run()——>线程体
方法一:直接继承Thread类
step1:创建一个子类,来继承Thread类 step2:重写run()方法,因为这是线程体:当CPU调度执行该线程的时候,就要执行的是run()方法中的代码。 step3:创建该类的对象,表示一个线程,调用start()进而启动这个线程。意味着该线程一切准备就绪,随时可以被CPU调度执行。但是CPU是否立刻执行?不一定,要看CPU自己。
方法二:实现Runnable接口
step1:创建一个实现类,实现Runnable接口 step2:重写run()方法 step3:先创建该实现类对象:mt,根据实现类对象再创建Thread对象,然后启动。
Thread类的构造方法:
Thread();//创建一个线程对象,执行run()。线程的默认名:Thread-0,1,2... Thread(Runnable target);//创建一个线程对象,指明了target,执行的run是Runnable接口中。 Thread(String name);//创建一个线程,并给起个名字 Thread(Runnable target,String name);
对比两种创建并启动线程的方式:
5. 线程常用方法
1、获取当前的线程对象:由Thread类直接调用,获取当前正在被执行的那个线程 static Thread currentThread() ;//返回对当前正在执行的线程对象的引用。 2、线程的名字:当一个线程创建的时候,如果没有设置名称:构造方法设置,或者setName()设置。系统默认的:Thread-0,Thread-1,Thread-2...... String getName() 返回此线程的名称。 void setName(String name) 将此线程的名称更改为等于参数 name 。 3、线程的Id:每个线程创建的时候,由系统自动分配一个Id,long类型的数值,终身不变。从线程的出生到死亡。 该Id值,由系统自动分配,程序员无法手动操作。 long getId() 返回此线程的标识符。 4、线程的优先级:priority System.out.println("最大优先级:"+Thread.MAX_PRIORITY);//10 System.out.println("最小优先级:"+Thread.MIN_PRIORITY);//1 System.out.println("正常优先级:"+Thread.NORM_PRIORITY);//5 当一个线程被创建的时候,由系统自动分配一个优先级,固定都是正常优先级:5 但是程序员可以根据需求,手动调整线程的优先级。 int getPriority() 返回此线程的优先级 void setPriority(int newPriority) 更改此线程的优先级。 有个坑:容易误解为:优先级别高的先执行,然后再执行优先级低的。大大的错误XXXXX 优先级别高,被CPU调度执行的机会就多。但是不绝对。 优先级别低,被CPU执行的机会就少,但是也不绝对。 5、线程的睡眠:重要的方法 static void sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 静态方法,应该由类直接调用,对象也可以调用,有坑:不是谁调用就谁睡,而是当前正在执行的线程进入睡眠了。和谁调用无关。 阻塞: 6、线程合并 void join() 等待这个线程死亡。 t1线程,t2线程,main线程 t1,t2,main--->3条线程抢占资源 某一个时刻:main线程中:t1.join(),主线程要等待t1线程死亡之后再执行 t1,t2--->2条线程抢占资源,main等 t1结束后,main线程再执行 阻塞: Scanner scan = new Scanner(System.in); scan.nextInt();//阻塞式。等--->解除阻塞,读取到一个键盘输人 7、守护线程 setDaemon(); 为前台线程服务,如果所有的前台线程都结束了,那么守护线程也就结束了。 GC:垃圾自动回收机制。JVM启动后,创建主线程执行main()的时候。。。随之而创建并启动的还有很多后台线程,比如gc() 8、其他方法:
package com.qf.demo02; class MyThreadF extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(" "+Thread.currentThread().getName()+" "+i); } } } public class Test7Join { public static void main(String[] args) { MyThreadF t1 = new MyThreadF(); MyThreadF t2 = new MyThreadF(); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); for(int i =0;i<100;i++){ System.out.println("main..."+i); if(i == 50){ try { /* * 线程合并 void join() 等待这个线程死亡。 主线程中:t1.join(),主线程如果持有资源,会将机会让给t1线程。 t1线程在main线程之前执行。t1结束后,main线程才会执行。 */ t1.join();//? } catch (InterruptedException e) { e.printStackTrace(); } } } } }
package com.qf.demo02; class MyThreadG extends Thread{ private int i=0; public void run() { while(true){ System.out.println(Thread.currentThread().getName()+" "+i); i++; } } } public class Test8Daemon { public static void main(String[] args) { MyThreadG t1 = new MyThreadG(); //将t1线程设置为守护线程 t1.setDaemon(true); t1.start(); for(int i=0;i<1000;i++){ System.out.println(" main.."+i); } } }
6. 线程的状态
线程的生命周期:5种,6种,7种。
线程的一生,就好比秀女的一声。
线程new出来:新建
准备就绪,启动:start:就绪状态
如果被CPU调度执行:运行状态,run()方法
出生-->就绪-->运行-->死亡
7. 临界资源的安全问题
多个线程访问共享的数据,临界资源。
多个线程之间存在共享的数据。一条线程执行过程中,其他线程也可以访问,可能会修改数据的值。造成的共享数据的不安全。叫做临界资源的安全问题。
package com.qf.demo03; class MyThread1 implements Runnable{ private int tickets = 10; private Object obj = new Object();//成员变量,属于对象 @Override public void run() { while(true){ /* * 需要使用synchronized同步代码块,防止共享数据存在不安全性。 * * 锁定的对象:谁作为锁头? * this:mt * obj: * "abc":所谓的大招,字面常量,内存中独一份。 */ //t1,t2,t3,t4 synchronized (this) {//任意对象,只要是这4个线程共同的对象即可。 t1给obj上锁 if(tickets > 0){ try { Thread.sleep((int)(Math.random()*101));//t1睡去了。。释放cpu资源 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",出售:"+tickets); tickets--; }else{ System.out.println(Thread.currentThread().getName()+",售票结束。。"); break; } } } } } public class Test9_SaleTicket { public static void main(String[] args) { /* * 2.模拟火车站卖票:使用4个线程,模拟4个售票口,出售共同的100张票。 分别使用两种创建线程的方式实现。 思路: 四个售票口:4条线程:t1,t2,t3,t4 出售100张票:int ticket:100,99,98,97.。。。。1 */ MyThread1 mt = new MyThread1(); Thread t1 = new Thread(mt, "售票口1"); Thread t2 = new Thread(mt, "售票口2"); Thread t3 = new Thread(mt, "售票口3"); Thread t4 = new Thread(mt, "售票口4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
同步synchronized
同步的方式一:同步代码块
synchronized(锁对象){//上锁 //被同步的代码,每次只能被一个线程执行,中间不能被其他线程插入执行 }//锁打开
注意点:
同步的原理:锁定一个对象。(对象可以和程序无关,但必须是多个线程访问的共同的对象才可以)。
常用的锁对象:
this,创建一个对象,传入进去。
大招:类名.class,字符串常量:"abc"
package com.qf.demo03; class MyThread2 extends Thread{ private static int ticket = 10; // private static Object obj = new Object();//成员变量 private Object obj; public MyThread2(String name,Object obj) { super(name); this.obj = obj; } @Override public void run() { while(true){ /* * 如果是继承Thread类的方式,不能用this作为锁对象,因为创建多个子线程对象,this其实指代的是多个对象,不是同一个,锁不住 * * 大招: * 字符串常量:"abc" * 类名.class * * java程序:源代码-->class * 进行编译:class字节码文件 * 万事万物皆对象:字节码文件---->类和它对应的:Class * * * * Class类 * 对象.getClass()-->Class * 类名.class-->Class * Class.forName("包名.类名")-->Class */ synchronized (MyThread2.class) { if(ticket > 0){ try { Thread.sleep((int)(Math.random()*100)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",出售: "+ticket); ticket--; }else{ System.out.println(Thread.currentThread().getName()+",售票结束。。"); break; } } } } } public class Test10_SaleTicket { public static void main(String[] args) { Object obj = new Object();//作为锁对象 MyThread2 t1 = new MyThread2("售票口1",obj); MyThread2 t2 = new MyThread2("售票口2",obj); MyThread2 t3 = new MyThread2("售票口3",obj); MyThread2 t4 = new MyThread2("售票口4",obj); t1.start(); t2.start(); t3.start(); t4.start(); } }
同步的方式二:同步方法
//该方法,每次只能有一个线程来执行,期间,不能被其他的线程插入执行。 public synchronized void 方法名(){ }
同步的优缺点:
-
解决了多线程之间的共享数据的安全问题
-
降低效率,容易死锁。
死锁:多个线程互相持有对象,僵持的现象。
解决死锁:
1、减少成员变量的使用。
2、加大锁的粒度。不要锁小对象,锁大对象。
package com.qf.demo03; class MyThread3 implements Runnable{ private static String bread = "面包"; private static String milk = "牛奶"; boolean flag ;//true, false @Override public void run() { if(flag == true){//t1 synchronized (bread) { System.out.println(Thread.currentThread().getName()+" ,已经拥有了"+bread+",还想要。。"+milk); synchronized (milk) { System.out.println(Thread.currentThread().getName()+",两者都拥有了。。"); } } }else{//t2 synchronized (milk) { System.out.println(Thread.currentThread().getName()+" ,已经拥有了"+milk+",还想要。。"+bread); synchronized (bread) { System.out.println(Thread.currentThread().getName()+",两者都拥有了。。"); } } } } } public class Test11_DeadLock { public static void main(String[] args) { /* * t1:张三 * t2:李四 * t1锁定break,要milk * t2锁定milk,要bread */ MyThread3 mt1 = new MyThread3(); MyThread3 mt2 = new MyThread3(); mt1.flag = true; mt2.flag = false; Thread t1 = new Thread(mt1,"张三"); Thread t2 = new Thread(mt2,"李四"); t1.start(); t2.start(); } }
以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章