1—线程解释
线程含义:
进程是指正在执行的程序,每个进程至少有一个线程,
线程是进程中的一个独立控制单元,线程控制着进程执行。
多线程是指多个线程同时进行,可以提高效率。
多线程具有随机性,因为多个线程共同抢夺CPU资源,某一时刻的CPU执行权不确定。JVM也是多线程,至少有主线程、垃圾回收机制两个线程。
线程的几种状态:
(1)创建:新建一个线程。
(2)运行:执行新建线程的方法。
(3)冻结:sleep(int time) 冻结固定时间;或者用wait(),直到被notify()唤醒。
(4)阻塞:线程的临时状态,等待CPU的执行权。
(5)消亡:线程执行完毕;或者stop()停止线程。
2—Thread类常用方法
字段方法:
int Thread.MIN_PRIORITY; //线程可具有的最低优先级 int Thread.MAX_PRIORITY; //线程可具有的最高优先级 int Thread.NORM_PRIORITY; //线程默认优先级
优先级可以通过: t.setPriority(Thread.MAX_PRIORITY) 设置线程优先级,优先级高的线程CPU执行它的频率就高一点。
线程优先级用整数 1-10 表示,但一般不写整数,不直观。构造方法:
Thread(String name); //分配一个指定线程名称的线程
常用方法:
void run(); //线程执行内容 void start(); //启动新线程 Thread currentThread(); //返回当前正在执行的线程对象的引用。它是静态的 String getName(); //返回当前线程的名称 void setName(); //设置线程名称 boolean isDaemon(); //该线程是否为守护线程 void setDaemon(); //设置线程为守护线程 int getPriority(); //获取线程优先级 void setPriority(); //设置线程优先级 void sleep(time); //使线程睡眠一段时间 void interrupt(); //中断线程 void join(); //等待该线程终止 void yield(); //暂停当前线程,执行其他线程 String toString(); //返回线程名称,线程优先级,线程组
从Object类继承的方法:
final void wait(); //线程进入等待状态 final void wait(time); //线程等待指定毫秒时间 final void notify(); //线程唤醒,一般唤醒第一个等待的线程 final void notifyAll(); //唤醒所有等待线程
创建线程的第一种方式:
继承Thread,
复写run方法,
start方法开启新线程并执行run方法。注意: run方法用于存储线程要运行的代码,start负责开启新线程并执行run方法。
举例:
class Demo extends Thread //定义类继承Thread { public void run() //复写run方法 { for (int x=0;x<100;x++) { System.out.println("run---"+x); } } } class ThreadDemo { public static void main(String [] args) { Demo d1=new Demo(); //创建一个新线程。 Demo d2=new Demo(); //创建另一个线程。 d1.start(); //启动新线程,并执行run方法。 d2.start(); for (int x=0;x<100;x++) { System.out.println("main-----"+x); } } }
注意: 创建的线程都有默认名称,Thread-0、Thread-1、可以重命名。
多线程练习:窗口售票程序–继承 Thread 方式:
class Ticket extends Thread { private static int t=100; Ticket(String name) { super(name); } public void run() { while (t>0) { System.out.println(Thread.currentThread().getName()+"---"+t); t--; } } } class Demo { public static void main(String [] args) { new Ticket("窗口1").start(); new Ticket("窗口2").start(); new Ticket("窗口3").start(); new Ticket("窗口4").start(); } }
3—Runnable 接口
Runnable接口只有一个抽象run方法 ,需要由子类覆盖。
创建线程的第二种方式:
(1)定义类实现 Runnable 接口,
(2)覆盖 Runnable 中的run方法,
(3)通过Thread类建立线程对象,
(4)将Runnable接口的子类对象作为参数传递给Thread的构造函数。
(5)调用Thread类的start方法开启新线程,并执行Runnable中的run方法。多线程练习:窗口售票- -实现 Runnable 方式:
class Ticket implements Runnable { private int t=100; public void run() { while (t>0) { System.out.println(Thread.currentThread().getName()+":::"+t); t--; } } } class Demo { public static void main(String [] args) { Ticket t=new Ticket(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
两种创建线程方式的区别:
实现Runnable创建线程避免了继承Thread创建线程的局限性。java只能单继承,类继承了Thread就不能再继承其他类,而实现没有这个局限性。
创建线程一般使用实现Runnable接口的方式。
4—多线程的安全问题
多线程同时操作一个共享数据时,有的线程对多条语句只执行了一部分,另一个线程就参与进来了,导致了共享数据错误。
这时可以让操作共享数据的多条语句全部执行完再允许其他线程参与进来。java提供同步代码块以解决线程安全问题,形式:
synchronized (duixiang) { 需要被同步的代码; }
注意: 同步的两个前提:必须是多线程,必须是同一个锁。
synchronized 相当于是一个锁,锁在里面的线程可以同步执行,锁在外面的线程进不来。
只有锁内的语句执行完,锁释放了之后,才可以允许下一个线程进来,就像WC一样,只能一个一个来。同步代码块解决了线程安全问题,但是每次都要判断锁的开关,比较消耗资源。
线程安全的售票程序:
class Ticket implements Runnable { private int t=100; Object oo=new Object(); //建立一个对象锁。 public void run() { while(true) { synchronized(oo) { //锁的是同一个Object对象。 if (t>0) { try {Thread.sleep(10);} catch (Exception e) {} //让该线程等待10毫秒。 System.out.println(Thread.currentThread().getName()+":::"+ t--); } } } } } class Demo { public static void main(String [] args) { Ticket t=new Ticket(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
注意:
如果该程序不加同步代码块,线程等待时CPU执行权会被抢走。
如果t=1时执行权被抢走,其他线程进来判断t还是等于1,
等到线程醒来时,打印语句会被执行多次,所以 t 的值就可能为负。同步函数:
当需要被同步的代码都在一个方法中时,可以对这个方法加锁,
这就是同步函数。
形式:public synchronized void method() { 需要被同步的代码; }
如果函数内的代码不全是需要被同步的代码,可以将需要被同步的代码封装成同步函数然后调用。
形式:public void method() { while (true) { show(); } } public void show() { 需要被同步的代码; }
注意: 锁用在函数上时,它持有的是本类对象的锁,也就是 this 。
当同步函数是静态的,该函数就是静态同步函数。
静态函数的锁不能再是this了,因为this代表的对象还没有创建。
静态同步函数的锁是class对象,因为内存中只有class对象是在静态之前存在的。死锁:同步中嵌套同步。
死锁是指两个锁互相调用时,导致程序挂起。
举例:class Demo { public static void main(String [] args) { Thread t1=new Thread(new Suo(true)); Thread t2=new Thread(new Suo(false)); t1.start(); t2.start(); } } class Suo implements Runnable { private boolean bool; Suo(boolean bool) { this.bool=bool; } public void run() { if (bool) { while (true) { synchronized (Demo.class) { try {Thread.sleep(10);} catch(Exception e) {} System.out.println("o1 true"); synchronized (Suo.class) { try {Thread.sleep(10);} catch(Exception e) {} System.out.println("o2 true"); } } } } else { while (true) { synchronized (Suo.class) { try {Thread.sleep(10);} catch(Exception e) {} System.out.println("o2 false"); synchronized (Demo.class) { try {Thread.sleep(10);} catch(Exception e) {} System.out.println("o1 false"); } } } } } }
5—线程间的通讯
多个线程轮流操作同一资源。
举例:多线程操作同一资源,注意线程安全问题。
class Res { String name; String sex; } class In implements Runnable { private Res r; In(Res r) { this.r=r; } public void run() { int x=0; while (true) { synchronized (r) { if (x==0) { r.name="你你"; r.sex="女"; } else { r.name="nini"; r.sex="nv"; } x=(x+1)%2; } } } } class Out implements Runnable { private Res r; Out(Res r) { this.r=r; } public void run() { while (true) { synchronized (r) { System.out.println(r.name+"++"+r.sex); } } } } class Demo { public static void main(String [] args) { Res r=new Res(); new Thread(new In(r)).start(); new Thread(new Out(r)).start(); } }
注意: 如果不加锁,对name和sex的赋值和取出可能不同步,就有可能打印出中英文混合的情况。
如果想要让程序交替输出,就要让循环体执行一次就交换。
就要用到等待唤醒机制,即循环体执行一次之后就等待,让另一个线程的循环体执行;
然后反复交换,以达到轮流执行的目的。举例:多线程交替执行操作同一资源的例子。
class Res { String name; String sex; boolean bool; public synchronized void set(String name,String sex) { if (bool) { try {wait();} catch (Exception e) {} } this.name=name; this.sex=sex; bool=true; notify(); } public synchronized void get() { if (!bool) try {wait();} catch (Exception e) {} System.out.println(name="++"+sex); bool=false; notify(); } } class In implements Runnable { Res r=new Res(); In(Res r) { this.r=r; } public void run() { int x=0; while (true) { if (x==0) r.set("nini","nv"); else r.set("你你","女"); x=(x+1)%2; } } } class Out implements Runnable { Res r=new Res(); Out(Res r) { this.r=r; } public void run() { while (true) { r.get(); } } } class Demo { public static void main(String [] args) { Res r=new Res(); new Thread(new In(r)).start(); new Thread(new Out(r)).start(); } }
注意:
本例中,当资源中的bool为假时,说明name和set没有被取走,
这时,就要让In类wait,Out类被notify,
bool为真时,就反过来。
bool为真时,x=1执行一次中文赋值,x=0执行一次英文赋值,
可以在控制台看到打印室交替执行的。当多个线程操作同一资源时,就有可能出现不同步的情况。
举例:多个输入线程、多个输出线程 操作统一资源。
class Res { private String name; private boolean bool; private int count=1; public synchronized void set(String name) { while (bool) { try {wait();} catch (Exception e) {} } this.name=name; System.out.println(this.name+"--Made--"+ ++count); bool=true; notifyAll(); } public synchronized void get() { while (!bool) { try {wait();} catch (Exception e) {} } System.out.println(name+" -Buy-"+count); bool=false; notifyAll(); } } class Made implements Runnable { Res r=new Res(); Made(Res r) { this.r=r; } public void run() { while (true) { r.set("-商品-"); } } } class Buy implements Runnable { Res r=new Res(); Buy (Res r) { this.r=r; } public void run() { while (true) { r.get(); } } } class Demo { public static void main(String [] args) { Res r=new Res(); new Thread(new Made(r)).start(); new Thread(new Made(r)).start(); new Thread(new Buy(r)).start(); new Thread(new Buy(r)).start(); } }
注意:
本例中,多个生产线程,多个消费线程,需要用到notifyAll方法。
它代表唤醒等待中的所有线程,如果只用notify,可能唤醒本方的等待线程。小知识点:java1.5版本以后,出现了 Lock 类,它替换了synchronized方法,并且将Object中的 wait notify notifyAll 替换成了condition对象。
使用时需要导包 : import java.util.concurrent.locks.*;
Lock 常用方法:
void lock(); //获取锁。 void unlock(); //释放锁。 condition newCondition(); //返回绑定此Lock的新condition。
Condition 常用方法:
void await(); //线程等待 void signal(); //唤醒一个等待线程 void signalAll(); //唤醒所有等待线程
举例:对同一资源的生产消费。
import java.util.concurrent.locks.*; class Res { private String name; private boolean bool; private int count=1; private Lock lock=new ReentrantLock(); private Condition con_1=lock.newCondition(); private Condition con_2=lock.newCondition(); public void set(String name) throws InterruptedException { lock.lock(); try { while (bool) {con_1.await();} this.name=name; System.out.println(this.name+"-MADE-"+ ++count); bool=true; con_2.signal(); } finally { lock.unlock(); } } public void get() throws InterruptedException { lock.lock(); try { while (!bool) {con_2.await();} System.out.println(name+"-BUY-"+count); bool=false; con_1.signal(); } finally { lock.unlock(); } } } class Made implements Runnable { Res r=new Res(); Made(Res r) { this.r=r; } public void run() { while (true) { try {r.set("商品");} catch(InterruptedException e){} } } } class Buy implements Runnable { Res r=new Res(); Buy(Res r) { this.r=r; } public void run() { while (true) { try {r.get();} catch(InterruptedException e) {} } } } class Demo { public static void main(String [] args) { Res r=new Res(); new Thread(new Made(r)).start(); new Thread(new Buy(r)).start(); } }
注意:
(1)使用Lock类时,同一个线程的等待和唤醒不是同一个condition对象。
(2)condition对象要抛异常。
(3)使用Lock类在共享资源中要声明Lock和至少两个Condition对象,
格式:Lock lock=new ReentrantLock(); Condition con_1=lock.newCondition(); Condition con_2=lock.newCondition();
6—对多线程操作的其他方法
停止线程的方式: interrupt
interrupt将处于冻结状态的线程强制恢复到运行状态。
但是会抛 InterruptedException 异常。
interrupt 是在run方法处于冻结状态时的一种暴力唤醒方式。举例:
class Interrupt implements Runnable { private boolean bool=true; public synchronized void run() { while (bool) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"Exception run."); bool=false; } System.out.println("run run."); } } } class Demo { public static void main(String [] args) { Interrupt in=new Interrupt(); Thread t1=new Thread(in); Thread t2=new Thread(in); t1.start(); t2.start(); for (int x=0;x<100;x++) { if (x==99) { t1.interrupt(); t2.interrupt(); break; } System.out.println(x); } System.out.println("main over"); } }
注意:捕获到 InterruptedException ,说明是被interrupt暴力唤醒了。
可以在catch中定义标记,结束run方法。守护线程:
也叫后台线程,守护线程是一种伴随状态的线程。
线程在建立之后,如果在t.start() 前加: setDaemon(true),就可以将该线程标记成守护线程。
举例:class Interrupt implements Runnable { private boolean bool=true; public synchronized void run() { while (bool) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"Exception run."); bool=false; } System.out.println("run run."); } } } class Demo { public static void main(String [] args) { Interrupt in=new Interrupt(); Thread t1=new Thread(in); Thread t2=new Thread(in); t1.setDaemon(true); t2.setDaemon(true); t1.start(); t2.start(); for (int x=0;x<100;x++) { if (x==20) { t1.interrupt(); t2.interrupt(); } System.out.println(x); } System.out.println("main over"); } }
注意:
设置守护线程的方法必须在启动线程前调用。
所有前台线程都结束后,守护线程也不再执行。
当正在运行的线程都是后台线程时,JVM退出。join方法:
用于临时暂停当前线程。
t.join() 代表的是当前线程暂停执行,先执行t线程,t线程执行完毕,才执行当前线程。
举例:class Join implements Runnable { public void run() { for (int x=0;x<20;x++) { System.out.println("Join run"+x); } } } class Demo { public static void main(String [] args) throws Exception { Join j=new Join(); Thread t1=new Thread(j); Thread t2=new Thread(j); t1.start(); t2.start(); t1.join(); //z这里抛异常。 for (int x=0;x<30;x++) { System.out.println("main run"+x); } System.out.println("over"); } }
注意: join方法要抛异常。
join是把当前线程暂停一下,等到要加入的线程执行完毕,当前线程自动回复执行状态。
如果join方法之前开启了多个线程,当前线程只等待要加入的线程执行完就恢复执行。yield 方法:
暂停当前正在执行的线程,先执行其他线程。
多个线程同时运行时,有可能是某一个线程先执行很多次,然后轮到下一个线程执行很多次。
这样不协调,yield方法可以让线程执行一次后就缓和一下,先让其他线程执行,
这样就可以使线程更接近于运算均衡。
举例:class Join implements Runnable { public void run() { for (int x=0;x<20;x++) { System.out.println(Thread.currentThread().getName()+"Join run----"+x); Thread.yield(); //线程执行一次后,就缓和一下执行频率。 } } } class Demo { public static void main(String [] args) throws Exception { Join j=new Join(); Thread t1=new Thread(j); Thread t2=new Thread(j); t1.start(); t2.start(); t2.join(); //这里可以让主线程暂停一会,等待上两个线程先执行。 for (int x=0;x<30;x++) { System.out.println("MAIN"+x); } System.out.println("over"); } }
注意:
yield方法会使当前线程释放执行权,但是有可能下次还被此线程抢到。
所以yield方法不能绝对的保证线程交替执行。扩展知识:
多线程的简化写法:class Demo { public static void main(String [] args) { new Thread() { public void run() { for (int x=0;x<20;x++) { System.out.println(x+1); } } }.start(); Runnable r=new Runnable() { public void run() { for (int y=20;y<40;y++) { System.out.println(y+1); } } }; new Thread(r).start(); for (int z=40;z<60;z++) { System.out.println(z+1); } } }
注意:主线程的循环体要放最下面,否则主线程要等循环结束才能开启 其他线程。