Java学习14:多线程
来源:互联网 发布:perl from json 编辑:程序博客网 时间:2024/05/21 09:54
概述
进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:进程中独立的一个控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
Java VM启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且该线程运行的代码存在于main方法中,该线程称之为主线程。
扩展: 更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
class ThreadDemo{ public static void main(String[] args) { for(int i=0; i<4000; i++) System.out.println("hello world!"); }}
自定义线程
自定义一个线程-方法1
通过对api的查找,Java已经提供了对线程这类事物的描述,使用的是Thread类。
创建线程的第一种方式,继承Thread类,步骤如下:
(1)定义类继承Thread类;
(2)复写Thread类中的run方法,目的是将自定义的代码存储在run方法,让线程运行;
(3)调用线程的start方法,该方法有两个作用:启动线程和调用run方法。
class ThreadDemo extends Thread{ public void run() { for(int i=0; i<100; i++) System.out.println("ThreadDemo run!"); }} class Demo{ public static void main(String[] args) { ThreadDemo d = new ThreadDemo();//创建好一个线程 d.start();//开启线程并执行该线程的run方法 //d.run()不行,仅仅是对象调用方法,而线程虽然创建了,但是没有运行 for(int i=0; i<100; i++)//主线程 System.out.println("hello world!"); }}
发现运行结果每一次都不同,因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行,在某个时刻,只有一个程序在运行(多核除外),CPU在做着快速的切换,以达到看上去是同时运行的效果。
多线程运行时在互相抢夺CPU的执行权,此为多线程的一个特性:随机性,谁抢到谁执行,CPU决定执行时间。
为什么要覆盖run方法?
Thread用于描述线程, 该类就定义了一个功能,用于存储线程要运行的代码,该存储功能,就是run方法。
线程运行状态
线程都有自己默认的名称:Thread-编号,该编号从0开始。
class ThreadDemo extends Thread{ ThreadDemo(String name) { super(name); } public void run() { for(int i=0; i<10; i++) System.out.println(this.getName() + " ThreadDemo run!"); }}class Demo { public static void main(String[] args) { ThreadDemo d1 = new ThreadDemo("d1");//创建线程d1 ThreadDemo d2 = new ThreadDemo("d2");//创建线程d2 d1.start(); d2.start(); for(int i=0; i<10; i++) System.out.println("Hello World!"); }}
static Thread currentThread():获取当前线程对象;
getName():获取线程名称;
设置线程名称:setName()或者构造函数。
自定义一个线程-方法2
步骤如下:
(1)定义类实现Runnable接口;
(2)覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中;
(3)通过Thread类建立线程对象;
(4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数,因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象;
(5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
class Ticket implements Runnable{ private int tick = 100;//票数共享 public void run() { while(true) { if(tick > 0) { System.out.println(Thread.currentThread().getName() + " sale " + tick--); } } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 Thread t3 = new Thread(t);//创建一个线程 Thread t4 = new Thread(t);//创建一个线程 t1.start(); t2.start(); t3.start(); t4.start(); }}
两种方法(继承方式和实现方式)的区别
继承Thread:线程代码存放在Thread子类run方法中;
实现Runnable:线程代码存在接口的子类的run方法中,上例中tick变量共享;
实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。
**比如,**Student类已经继承了Person类,但是此时Student类中有个run方法需要多进程运行,此时不能再继承Thread,需要实现Runnable实现多进程。
多线程的安全问题
/*...Thread-0 sale 5Thread-1 sale 3Thread-3 sale 4Thread-2 sale 2Thread-3 sale 1Thread-1 sale 0Thread-0 sale -1Thread-2 sale -2*/class Ticket implements Runnable{ private int tick = 100;//票数共享 public void run() { while(true) { if(tick > 0) { try {Thread.sleep(100);}catch(Exception e){} System.out.println(Thread.currentThread().getName() + " sale " + tick--); } } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 Thread t3 = new Thread(t);//创建一个线程 Thread t4 = new Thread(t);//创建一个线程 t1.start(); t2.start(); t3.start(); t4.start(); }}
通过分析发现打印出0, -1, -2等错票,多进程的运行出现了安全问题。
问题原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。
多线程同步代码块
实现
synchronized(对象){ 需要被同步的代码//哪些语句在操作共享数据}
/*...Thread-3 sale 8Thread-3 sale 7Thread-3 sale 6Thread-3 sale 5Thread-3 sale 4Thread-3 sale 3Thread-3 sale 2Thread-3 sale 1*/class Ticket implements Runnable{ private int tick = 100;//票数共享 Object obj = new Object(); public void run() { while(true) { synchronized(obj)//同步锁置1,上锁,其他线程无法执行synchronized中的内容 { if (tick > 0) { try {Thread.sleep(100);} catch (Exception e) {} System.out.println(Thread.currentThread().getName() + " sale " + tick--);//同步锁置0,解锁,其他线程可以执行synchronized中的内容 } } } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 Thread t3 = new Thread(t);//创建一个线程 Thread t4 = new Thread(t);//创建一个线程 t1.start(); t2.start(); t3.start(); t4.start(); }}
原理
synchronized(对象){ 需要被同步的代码//哪些语句在操作共享数据}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的进程即使获取CPU的执行权也进不去,因为没有获取锁。
同步的前提
(1)必须要有两个或者两个以上的线程;
(2)必须是多个线程使用同一个锁,必须保证同步中只能有一个线程运行。
好处:解决了多线程的安全问题。弊端:多个线程都需要判断锁,较为消耗资源。
如何找问题
(1)明确哪些代码是多线程运行代码;
(2)明确共享数据;
(3)明确多线程运行代码中哪些语句是操作共享数据的。
同步函数的锁是this
将卖票的例程改为同步函数形式:
class Ticket implements Runnable{ private int tick = 100;//票数共享 public void run() { while(true) { show();//调用同步函数 } } public synchronized void show()//同步函数 { if(tick > 0) { try {Thread.sleep(100);}catch(Exception e){} System.out.println(Thread.currentThread().getName() + " sale " + tick--); } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 Thread t3 = new Thread(t);//创建一个线程 Thread t4 = new Thread(t);//创建一个线程 t1.start(); t2.start(); t3.start(); t4.start(); }}
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this。见下例:
//使用2个线程来买票,一个线程在同步代码块中,另一个线程在同步函数中,都在执行买票动作class Ticket implements Runnable{ private int tick = 100;//票数共享 boolean flag = true; //Object object = new Object(); public void run() { if(flag) { while (true) { synchronized(this)//需要将object换为this,使用同一个锁,都使用this锁,不然不同步 { if (tick > 0) { try {Thread.sleep(100);} catch (Exception e) { } System.out.println(Thread.currentThread().getName() + " 同步代码块 " + tick--); } } } } else while (true) show();//调用同步函数 } public synchronized void show()//同步函数 { if(tick > 0) { try {Thread.sleep(100);}catch(Exception e){} System.out.println(Thread.currentThread().getName() + " 同步函数 " + tick--); } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 t1.start(); try{Thread.sleep(10);}catch (Exception e){}//目的是让t1跑起来,判断完flag后再将其置为false t.flag = false; t2.start(); }}
静态同步函数的锁是Class对象
同步函数被静态修饰后,使用的锁不是this,而是该方法所在类的字节码文件对象:类名.class。
原因:静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,类名. class,该对象的类型是Class。
class Ticket implements Runnable{ private static int tick = 100;//票数共享 boolean flag = true; public void run() { if(flag) { while (true) { synchronized(Ticket.class)//同步锁为类的字节码文件对象 { if (tick > 0) { try {Thread.sleep(100);} catch (Exception e) { } System.out.println(Thread.currentThread().getName() + " 同步代码块 " + tick--); } } } } else while (true) show();//调用同步函数 } public static synchronized void show()//同步函数 { if(tick > 0) { try {Thread.sleep(100);}catch(Exception e){} System.out.println(Thread.currentThread().getName() + " 同步函数 " + tick--); } }}class Demo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t);//创建一个线程 Thread t2 = new Thread(t);//创建一个线程 t1.start(); try{Thread.sleep(10);}catch (Exception e){}//目的是让t1跑起来,判断完flag后再将其置为false t.flag = false; t2.start(); }}
单例设计模式-懒汉式
单例设计模式-懒汉式参考:http://blog.csdn.net/gsh_hello_world/article/details/78196310
懒汉式特点为实例的延迟加载,多线程访问懒汉式会出现安全问题,使用同步代码块或者同步函数解决,使用双重判断提高效率;加同步时,使用对象为该方法所在类的字节码文件对象:类名.class。
//饿汉式/*class Student{ private int age; private Student(){}//1. 构造函数私有化 private static final Student s = new Student();//2. 在类中创建一个本类对象 public static Student getInstance()//3. 提供一个方法可以获取到该对象 { return s; } void setAge(int age) { this.age = age; } int getAge() { return this.age; }}*///懒汉式 class Student { private int age; static Student s = null; public static Student getInstance() { if (s == null)//外层再次判断一下就不会每次都进行同步判断了 { synchronized (Student.class)//同步代码块 { if (s == null)//假如进了if后,进程1挂起,进程2执行新建了一个对象,进程1继续运行时会再次新建一个对象,造成安全问题 { s = new Student(); } } } return s; }// public static synchronized Student getInstance() {//同步函数,每次都要判断锁,比较耗费资源//// if (s == null)//假如进了if后,进程1挂起,进程2执行新建了一个对象,进程1继续运行时会再次新建一个对象,造成安全问题// {// s = new Student();// }// return s;// } void setAge(int age) { this.age = age; } int getAge() { return this.age; }}class Demo { public static void main(String[] args) { Student s = Student.getInstance(); s.setAge(10); System.out.println(s.getAge()); }}
死锁
进程t1在请求lockb,lockb被进程t2锁住;进程t2在请求locka,locka被进程t1锁住,造成死锁。
/* if locka else lockb*/class test implements Runnable{ private boolean flag; test(boolean flag) { this.flag = flag; } public void run() { if(flag) { synchronized(lock.locka) { System.out.println("if locka"); synchronized(lock.lockb) { System.out.println("if lockb"); } } } else { synchronized(lock.lockb) { System.out.println("else lockb"); synchronized(lock.locka) { System.out.println("else locka"); } } } }}class lock{ static Object locka = new Object(); static Object lockb = new Object();}class Demo{ public static void main(String[] args) { Thread t1 = new Thread(new test(true)); Thread t2 = new Thread(new test(false)); t1.start(); t2.start(); }}
- Java学习14:多线程
- JAVA学习(14) 多线程--Thread类
- JAVA学习(14) 多线程-Runnable接口
- Java学习,多线程
- Java多线程学习笔记
- Java学习:多线程(1)
- java多线程学习总结
- java多线程学习
- java多线程学习总结
- java多线程学习
- java多线程学习
- Java多线程学习笔记
- [Java]多线程学习网站
- java多线程学习1
- Java学习笔记---多线程
- Java多线程举例学习
- Java多线程学习总结
- java多线程学习
- C++ Primer 12章习题
- Hibernate框架笔记整理--二
- androidstudio打包失败
- Leetcode——第136题——Single Number
- Python format 格式化函数 转载菜鸟教程
- Java学习14:多线程
- 类中的函数重载
- 南阳oj 回文字符串
- SpringMVC实现图片上传
- [简单逻辑学]逻辑学的基本原理——断言
- python在linux中执行shell脚本的方法
- 2018中国共享生活方式大会
- leetcode 5 Longest Palindromic Substring
- 171219-C++补漏【连续第五十五天】