Java学习笔记(多线程_1)
2015-11-26 19:10
716 查看
15 多线程
15.1 概念
进程:正在进行中的程序;每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。线程:进程中负责程序执行的控制单元(执行路径),线程在控制着进程的执行。
一个进程中至少有一个线程;
一个进程中可以由多个执行路径,称为多线程;
开启多线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
15.2 创建线程的方法
15.2.1 方法一:继承Thread类
通过对api的查找,java已经提供了对线程这类事物的描述,即Thread类。定义类继承Thread;
重写Thread类中的run方法;目的:将自定义的方法存储在线程中,让线程运行;
调用线程的start方法,该方法两个作用:启动线程和调用run方法;
例1
class Demo extends Thread{ public void run(){ for(int x =0;x<6;x++){ System.out.println("demo run"+x); } } } public class ThreadDemo { public static void main(String[] args) { Demo d = new Demo();//创建了一个线程 d.start();//开启线程并执行该线程的run方法 //d.run();//仅仅对象调用方法,而线程创建了并未运行。 for(int x =0;x<5;x++){ System.out.println("Hello"+x); } } }
运行结果:
创建线程的目的就是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务。
为何要重写run方法:
Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
多线程运行状态:
例2
class Demo extends Thread{ Demo(String name){ super(name); } public void run(){ for(int x =0;x<6;x++){ System.out.println((Thread.currentThread()==this)+"..."+this.getName()+".run.."+x); } } } public class ThreadDemo { public static void main(String[] args) { Demo d = new Demo("one");//创建了一个线程 Demo d1= new Demo("two"); d.start(); d1.start(); for(int x =0;x<5;x++){ System.out.println("Hello"+x); } } }
PS:
1. 可通过Thread的getName()方法获取线程的名称,名称格式如下:Thread-编号(0开始);
2. Thread创建时就已经命名了;
3. currentThread();获取当前线程对象;
4. 设置线程名称:setName或者构造函数。
15.2.2 方法二:实现Runnable接口
步骤:定义类 Runnable接口;
重写Runnable接口中的run方法;将线程要运行的代码放在该run方法中;
通过Thread类建立线程对象;
将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;
调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
例3
class Ticket1 implements Runnable{ private int tick = 8; public void run(){ while(true){ if(tick>0) System.out.println(Thread.currentThread().getName()+ "...sale.."+tick--); } } } public class TicketDemo { public static void main(String[] args) { Ticket1 t = new Ticket1(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.start(); t2.start(); t3.start(); } }
运行结果:
实现方式和继承方式的区别:
实现Runnable接口避免了单继承的局限性,在定义线程时,建议使用实现方式;
继承Thread,线程代码存放在Thread子类run方法中;实现Runnable,线程代码存在接口的子类run方法中。
15.2.3 线程安全
代码:class Ticket1 implements Runnable{ private int tick = 10; public void run(){ while(true){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "...sale.."+tick--); } } } } public class TicketDemo { public static void main(String[] args) { Ticket1 t = new Ticket1(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.start(); t2.start(); t3.start(); } }
运行结果:
打印出tick小于等于0的结果,
问题原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
采用同步代码块:
synchronized(对象){ 需要被同步的代码; }
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权也进不去,因为没有获取锁。
使用前提必须有多个线程并使用同一个锁。
修改后的代码:
class Ticket1 implements Runnable{ private int tick = 10; Object obj = new Object(); public void run(){ while(true){ synchronized(obj){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "...sale.."+tick--); } } } } } public class TicketDemo { public static void main(String[] args) { Ticket1 t = new Ticket1(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.start(); t2.start(); t3.start(); } }
同步代码块的弊端:多个线程需要判断锁,较为消耗资源,会降低程
序的运行效率。
如何找问题:
明确哪些代码是多线程运行代码;
明确共享数据;
明确多线程运行代码中哪些语句是操作共享数据的。
例4:
class Bank{ private int sum; public void add(int n){ sum += n; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sum="+sum); } } class Cus implements Runnable{ private Bank b = new Bank(); public void run() { for(int x=0;x<3;x++){ b.add(100); } } } public class BankDemo { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); Thread t3 = new Thread(c); t1.start(); t2.start(); t3.start(); } }
运行结果:
修改后:
class Bank{ private int sum; Object obj = new Object(); public synchronized void add(int n){//同步函数 //synchronized(obj){ sum += n; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sum="+sum); //} } } class Cus implements Runnable{ private Bank b = new Bank(); public void run() { for(int x=0;x<3;x++){ b.add(100); } } } public class BankDemo { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); Thread t3 = new Thread(c); t1.start(); t2.start(); t3.start(); } }
同步的两种表现形式:同步代码块和同步函数。
Ps:
1. 同步函数的锁是固定的this;
2. 同步代码块的锁是任意的对象。
由于同步函数的锁是固定的this,同步代码块是锁的任意的对象,如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
class Ticket implements Runnable{ private int tick = 10; boolean flag = true; //Object obj = new Object(); public void run(){ if(flag){ while(true){ synchronized(this){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "...code.."+tick--); } } } }else{ while(true) show(); } } public synchronized void show(){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "..function.."+tick--); } } } public class TicketDemo { 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);//线程休眠目的是为了使t1真正启动后,flag才设置为false; } catch (InterruptedException e) { e.printStackTrace(); } t.flag=false; t2.start(); } }
运行结果:
如果同步函数被静态修饰后,使用的锁不是this,而是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
class Ticket implements Runnable{ private static int tick = 10; boolean flag = true; public void run(){ if(flag){ while(true){ synchronized(Ticket.class){//this.getClass() if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "...code.."+tick--); } } } }else{ while(true) show(); } } public static synchronized void show(){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "..function.."+tick--); } } } public class TicketDemo { 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);//线程休眠目的是为了使t1真正启动后,flag才设置为false; } catch (InterruptedException e) { e.printStackTrace(); } t.flag=false; t2.start(); } }
15.2.4 多线程中单例模式
1. 饿汉式class Single{ private static final Single s = new Single(); private Single(){} public static Single getInstance(){ return s; } }
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
2. 懒汉式
class Single{ private static Single s = null; private Single(){} public static Single getInstance(){ if(s==null){ synchronized(Single.class){ if(s==null) s =new Single(); } } return s; } }
懒汉式存在安全问题,可以使用同步函数解决。
方式一:
public static synchronized Single getInstance(){ if(s==null) s =new Single(); return s; }
该方式使用效率较低,每次均需要判断;
方法二:
public static Single getInstance(){ if(s==null){ synchronized(Single.class){ if(s==null) s =new Single(); } } return s; }
原因在于任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,而不用判断是否能够获取锁,相对于上面使用同步函数的方法就提升了效率。如果当前线程发现Single对象尚未创建,则
再判断是否能够获取锁。
对于单例而言,只有一个对象,因此如果对象已经创建则可直接获取;
如果没有对象,则进行判断获取锁,
如果能够获取锁,那么就通过第二个if判断语句判断是否需要创建Single对象。因为可能当此线程获取到锁之前,已经有一个线程创建完Single对象,并且放弃了锁。此时它便没有必要再去创建,可以直接跳出同步代码块,放弃锁,获取Single对象即可。如果有必要,则再创建。
如果不能获取到锁,则等待,直至能够获取到锁为止,再按步骤一执行。
15.2.5 死锁
同步嵌套的死锁class Ticket implements Runnable{
private static int tick = 10;
boolean flag = true;
Object obj = new Object();
public void run(){ if(flag){ while(true){ synchronized(obj){//this.getClass() show(); } } }else{ while(true) show(); } } public synchronized void show(){ synchronized(obj){ if(tick>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "..function.."+tick--); } } } } public class TicketDemo { 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);//线程休眠目的是为了使t1真正启动后,flag才设置为false; } catch (InterruptedException e) { e.printStackTrace(); } t.flag=false; t2.start(); } }
相关文章推荐
- Struts2的国际化
- java环境变量的配置
- java反射实现动态代理
- eclipse properties文件编码插件
- 关于java.lang.IncompatibleClassChangeError: Implementing class错误解决
- 浅谈struts2
- Eclipse卡死解决汇总
- 获取jar包内部的资源文件
- 安卓(java)判断文件是否存在
- Java线程的生命周期
- java中urlrewrite的配置和使用
- java 四舍五入 保留俩位小数
- Eclipse - Memory Analyzer
- SpringMVC源码解析(下)
- java rar解压
- Eclipse Building Workspace 编译慢 解决办法
- Spring MVC(二)基于注解的登陆界面获取用户名和密码并跳转其它页面
- java socket报 connection reset的原因和解决方式
- SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
- [Java学习] Java虚拟机(JVM)参数简介