多线程学习笔记
进程是一个正在执行的程序
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
Java VM 启动的时候会有一个进程java.exe
一个进程中至少有一个线程负责Java程序的运行,而且这个线程运行的代码存放在main方法中,该线程称之为主线程
扩展:其实更细节说明虚拟机,虚拟机不止启动一个线程,还有负责垃圾回收的线程。
第一种创建线程的步骤:
1.创建类继承Thread类或者实现Runable接口
2.复写Run方法
3.调用线程的start方法
该方法有两个作用:启动线程 ,调用run方法
示例如下:假设有4个售票窗口同时进行售票,每一个售票窗口可以理解为一个线程,四个窗口同时售票
也就是四个线程同时启动,而假定票数是10张,这样....每个窗口也就是线程都在运行时产生四个对象,会
有40张票,即每个对象产生10张票,一个线程,总共40张票4个线程,为了解决这个问题,可以考虑将
Ticket类中的num设置为static使四个对象共享这10张票。
Ticket类中的num设置为static使四个对象共享这10张票。//Ticke类示例代码如下:public class Ticket extends Thread{//实现线程需继承Thread类private static int ticket_num=10;//静态共享数据假定为10张票@Overridepublic void run() {//复写run方法while(ticket_num>0){if(ticket_num>0){System.out.println(Thread.currentThread().getName()+"售票"+--ticket_num);//Thread为类名,currentThread()为静态方法,可以由类名调用,作用是返回当前线程对象,getName(),拿到当前线程的名字}}}}//TestMain示例代码如下:public class TestMain {public static void main(String[] args) {Ticket t1=new Ticket();//创建四个Thread类的子对象Ticket t2=new Ticket();Ticket t3=new Ticket();Ticket t4=new Ticket();t1.start();t2.start();t3.start();t4.start();//启动这四个对象的线程}}public class Ticket implements Runnable{private String name;//售票窗口名字private int num;//票数Ticket(String name,int num){this.name=name;this.num=num;}public void run() {//复写run方法while(this.num>0){System.out.println(Thread.currentThread().getName()+"售票"+this.num--);}}}//TestMain程序代码如下:public class TestMain {public static void main(String[] args) {Ticket t1=new Ticket("售票窗口",10);//创造实现Runnable接口的子类对象new Thread(t1).start();//开启3个线程new Thread(t1).start();new Thread(t1).start();}}/** * 售票类 有两个线程 同步代码块和同步函数 注意同步函数的锁为this而同步代码块的锁要达到 * 安全的目的则必须与同步函数的锁相同 也就是this * @author bing * */public class Ticket implements Runnable{private static int tickt=10;Object obj = new Object();boolean flag=true;public void run()//同步代码块{if(flag){while(true){//synchronized (obj)//注意这里的锁是obj,这样与同步函数用的不是一个锁,所以会产生数据异常,比如售票时卖到0号票..//synchronized (this)//这里用this锁,与同步函数用同一个锁{if(tickt>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println("同步代码块进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}}else{while(true){show();}}}public static synchronized void show()//同步函数{if(tickt>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println("同步函数进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}//测试程序代码如下:/** * 验证线程锁,同步代码块和同步函数的控制通过flag旗帜来控制 * 注意主线程需要sleep一段时间 * @author bing * */public class TestMain {public static void main(String[] args) throws InterruptedException {Ticket aTicket=new Ticket();Thread t1=new Thread(aTicket);Thread t2=new Thread(aTicket);t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会Thread.currentThread().sleep(10);aTicket.flag=false;System.out.println("-------------------------------------");t2.start();}}package DemoSynchronized_Static;/** * 售票类 有两个线程 同步代码块和同步函数 注意同步函数的锁为this而同步代码块的锁要达到 * 安全的目的则必须与同步函数的锁相同 也就是this * @author bing * */public class Ticket implements Runnable{private static int tickt=10;Object obj = new Object();boolean flag=true;public void run()//同步代码块{if(flag){while(true){//synchronized (obj)//注意这里的锁是obj,这样与同步函数用的不是一个锁,所以会产生数据异常,比如售票时卖到0号票..synchronized (Ticket.class)//这里用的是所在类的字节码文件对象,即类名.calss{if(tickt>0){try{Thread.sleep(100);}catch(Exception e){}System.out.println("同步代码块进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}}else{while(true){show();//调用同步函数}}}public static synchronized void show()//同步函数{if(tickt>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println("同步函数进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}//测试程序代码如下:/** * 验证线程锁,同步代码块和同步函数的控制通过flag旗帜来控制 * 注意主线程需要sleep一段时间 * @author bing * */public class TestMain {public static void main(String[] args) throws InterruptedException {Ticket aTicket=new Ticket();Thread t1=new Thread(aTicket);Thread t2=new Thread(aTicket);t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会Thread.currentThread().sleep(100);aTicket.flag=false;System.out.println("-------------------------------------");t2.start();System.out.println("+++++++++++++++++++");}}package Single_Design_Pattern_Multithreading;/** * 懒汉式单例模式 * 本类描述怎么在懒汉单例模式中运用多线程同步锁 * 方法一:可以直接将getInstance()方法声明为synchronized的 但是这样做 * 会浪费资源 每次调用getInstance()方法时都要判断锁,所以可以按以下的方式解决 * 也就是方法二--两次判断,延时加载 * * @author bing * */public class LazyMan {private static LazyMan lm=null;private LazyMan(){}public static LazyMan getInstance(){/**这里的处理方式为延迟加载比如有线程A 进入 进入aaa判断 再进入bbb判断这时线程B启动 由aaa判断后 到bbb此时 有线程锁的存在 线程B没有访问权限 再次回到线程A 线程A继续执行到ccc这时判断后建立对象lm 线程A解锁 这时线程B再执行 判断ccc结果对象lm已经存在了 所以不再执行。线程C启动 判断aaa时就lm已经存在了 所以也不会再继续这样 就避免了对 线程锁的重复判断 节省资源---这种方式叫做两次判断,延时加载*/if(lm==null)//aaa{synchronized (LazyMan.class)//bbb{if(lm==null)//cccc{lm=new LazyMan();}}}return lm;}}//饿汉式package Single_Design_Pattern_Multithreading;/** * 饿汉式单例设计模式,不存在共享数据异常的问题 所以不用锁 * @author bing * */public class HungryMan_Single {private static final HungryMan_Single hs=new HungryMan_Single();private HungryMan_Single(){}public static HungryMan_Single getInstance(){return hs;}}package Single_Design_Pattern_Multithreading;/** * 懒汉式单例模式 * 本类描述怎么在懒汉单例模式中运用多线程同步锁 * 方法一:可以直接将getInstance()方法声明为synchronized的 但是这样做 * 会浪费资源 每次调用getInstance()方法时都要判断锁,所以可以按以下的方式解决 * 也就是方法二--两次判断,延时加载 * * @author bing * */public class LazyMan {private static LazyMan lm=null;private LazyMan(){}public static LazyMan getInstance(){/**这里的处理方式为延迟加载比如有线程A 进入 进入aaa判断 再进入bbb判断这时线程B启动 由aaa判断后 到bbb此时 有线程锁的存在 线程B没有访问权限 再次回到线程A 线程A继续执行到ccc这时判断后建立对象lm 线程A解锁 这时线程B再执行 判断ccc结果对象lm已经存在了 所以不再执行。线程C启动 判断aaa时就lm已经存在了 所以也不会再继续这样 就避免了对 线程锁的重复判断 节省资源---这种方式叫做两次判断,延时加载*/if(lm==null)//aaa{synchronized (LazyMan.class)//bbb{if(lm==null)//cccc{lm=new LazyMan();}}}return lm;}}//饿汉式package Single_Design_Pattern_Multithreading;/** * 饿汉式单例设计模式,不存在共享数据异常的问题 所以不用锁 * @author bing * */public class HungryMan_Single {private static final HungryMan_Single hs=new HungryMan_Single();private HungryMan_Single(){}public static HungryMan_Single getInstance(){return hs;}}====================================================================================================死锁的问题 用以下的示例来说明死锁的问题package DemoSynchronized;public class Ticket implements Runnable{private int tickt=100;Object obj = new Object();public boolean flag=true;public void run()//同步代码块{if(flag){while(true){synchronized (obj)//这里是同步代码块中有同步函数,注意锁是不同的{show();}}}else{while(true){show();}}}public synchronized void show()//同步函数这里的锁是this{//注意这里是同步函数里面有同步代码块而且两者的锁是不同的synchronized(obj)//这里的锁是obj{if(tickt>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println("同步函数进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}}//测试程序代码如下:package DeadLock;import DemoSynchronized.Ticket;/** * 死锁: * 同步中嵌套同步 * @author bing * */public class TestMain {public static void main(String[] args) throws InterruptedException {Ticket aTicket=new Ticket();Thread t1=new Thread(aTicket);Thread t2=new Thread(aTicket);t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会Thread.currentThread().sleep(16);aTicket.flag=false;System.out.println("-------------------------------------");t2.start();}}//输出结果:输出过程中卡住 因为两个锁互相争夺package DemoSynchronized;public class Ticket implements Runnable{private int tickt=100;Object obj = new Object();public boolean flag=true;public void run()//同步代码块{if(flag){while(true){synchronized (obj)//这里是同步代码块中有同步函数,注意锁是不同的{show();}}}else{while(true){show();}}}public synchronized void show()//同步函数这里的锁是this{//注意这里是同步函数里面有同步代码块而且两者的锁是不同的synchronized(obj)//这里的锁是obj{if(tickt>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println("同步函数进程---"+Thread.currentThread().getName()+"-----sale:"+tickt--);}}}}//测试程序代码如下:package DeadLock;import DemoSynchronized.Ticket;/** * 死锁: * 同步中嵌套同步 * @author bing * */public class TestMain {public static void main(String[] args) throws InterruptedException {Ticket aTicket=new Ticket();Thread t1=new Thread(aTicket);Thread t2=new Thread(aTicket);t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会Thread.currentThread().sleep(16);aTicket.flag=false;System.out.println("-------------------------------------");t2.start();}}//输出结果:输出过程中卡住 因为两个锁互相争夺===========================================================================================线程间通信的安全问题:线程A对资源R进行加操作线程B对资源R进行减操作 如何同步 如何同步时安全操作 示例代码如下:package InOut_Demo;/** * 线程间通信的示例代码 * 线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作是不同的 * @author bing * */public class TestMain {public static void main(String[] args) {Res r =new Res();Input in=new Input(r);OutPut ou = new OutPut(r);Thread t1=new Thread(in);Thread t2=new Thread(ou);t1.start();t2.start();}}//资源R代码如下package InOut_Demo;/** * 资源Res,Input和OutPut的操作对象 * @author bing * */public class Res {private String name;private String sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}}//写操作代码如下package InOut_Demo;/** * 对资源R进行写操作 * @author bing * */public class Input implements Runnable{private Res r;Input(Res r){this.r=r;}public void run(){int x=0;while(true){synchronized (r) {if(x==0){r.setName("mike");r.setSex("man");}if(x==1){r.setName("王语嫣");r.setSex("女女");}}x=(x+1)%2;}}}//输出操作代码如下:package InOut_Demo;/** * 输出资源R的内容 * @author bing * */public class OutPut implements Runnable{private Res r;public OutPut(Res r){this.r=r;}public void run(){while(true){synchronized(r){System.out.println(r.getName()+r.getSex());}}}}//测试程序代码如下package InOut_Demo;/** * 线程间通信的示例代码 * 线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作是不同的 * @author bing * */public class TestMain {public static void main(String[] args) {Res r =new Res();Input in=new Input(r);OutPut ou = new OutPut(r);Thread t1=new Thread(in);Thread t2=new Thread(ou);t1.start();t2.start();}}public class Res {public String name;public String sex;public boolean flag=false;//旗帜变量用来控制是写资源还是输出资源}//写方法类如下:public class Input implements Runnable{public Res r;private int x=0;Input(Res r){this.r=r;}public void run() {while(true){synchronized(r){//如果旗帜为真,则等待 等待需要与锁一致if(r.flag){try{r.wait();}catch(Exception e){System.out.println("Input wait 异常");}}//如果旗帜为假 则写入资源if(x==0){r.name="mike";r.sex="man";}else{r.name="周芷若";r.sex="女女";}x=(x+1)%2;//写完资源后 资源设置为真r.flag = true;r.notify();//唤醒,需要与锁一致}}}}//输出资源类如下:package Wait_WakeUp_right;public class OutPut implements Runnable{private Res r;OutPut(Res r){this.r=r;}public void run(){while(true){synchronized(r){if(!r.flag)//如果资源为假 则等待 {try{r.wait();}catch(Exception e){System.out.println("output wait 出错");}}//如果资源为真 则输出资源System.out.println(r.name+"。。。。"+r.sex);r.flag = false;//输出后 资源设置为假r.notify();//唤醒}}}}//测试程序如下:public class TestMain {/**wait; * notify; * notifyALl; 都在同步中使用,因为要对持有监视器(锁)的线程操作。 * 所以都要使用在同步中,因为只有同步才有锁。 * 为什么这些操作线程的方法要定义在object类中? * 因为这些方法在操作同步中线程时,都必须要标识他们所操作的线程持有的锁 * 只有同一个锁上的被等待线程可以被同一个锁上的线程notify唤醒,不可以对不同 * 锁中的线程进行唤醒 * * 也即是说等待和唤醒必须是同一把锁。 * 而锁可以是任意对象,可以被任意对象调用的方法就定义在Object中 * * @param args */public static void main(String[] args) {Res r=new Res();Input in=new Input(r);OutPut ou=new OutPut(r);Thread t1=new Thread(in);Thread t2=new Thread(ou);t1.start();t2.start();}}