一、多线程的概念
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。
—-Java线程:概念与原理
二、多线程的创建与使用
创建线程的两个方法:Thread类和Runnable接口
Thread
创建线程的第一种方式:继承Thread类。
1、定义一个类继承Thread类。作用:创建线程
2、复写Thread类中的run方法。作用:创建线程功能
3、调用线程的start方法。作用:启动线程,调用run方法。若只用run,不用start则无法开启线程
代码如下:
package test; /** * @author wangkun */public class test4 {public static void main(String[] args){//定义一个类继承Thread类MyThread mt = new MyThread();//调用线程的start方法mt.start();for (int i = 0; i <60; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
class MyThread extends Thread{//复写Thread类中的run方法public void run(){for (int i = 0; i <60; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
java虚拟机启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
扩展: jvm启动不止一个线程,还有负责垃圾回收机制的线程。
1,为什么多线程运行的每次结果都不同?
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
2,为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。
(简而言之:有线程就要用Thread类,用Thread类就要用run方法,用该方法,就要覆盖即重写该方法。)
Runable
创建线程的第二种方式:实现Runable接口
步骤:
1,定义类实现Runnable接口
2,覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中。
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5,调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。
代码如下:
package test; /** * @author wangkun */public class test4 {public static void main(String[] args){//定义类实现Runnable接口Demo d =new Demo();//通过Thread类建立线程对象,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数Thread t = new Thread(d);//调用线程的start方法t.start();for(int x=0;x<60;x++){
System.out.println(Thread.currentThread().getName()+x);
}
}
}
class Demo implements Runnable{//覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。public void run(){for(int x=0;x<60;x++){
System.out.println(Thread.currentThread().getName()+x);
}
}
}
1、为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。简单来说,Thread要用run方法,而run方法又是Runnable接口的重写,则Thread应先与Runnable连接,才能使用run方法。
2、实现方式和继承方式有什么区别呢?
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法中。
实现方式好处:避免了单继承的局限性。
在定义线程时,建立使用实现方式。
三、多线程的安全问题
问题的原因
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:同步代码块
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。即解决方法为同步。
同步分两种:同步代码块、同步函数(将synchronized当作函数修饰符)。
如果在代码块前加上 synchronized关键字,则此代码块就成为同步代码块。
同步代码块的格式:
//判断锁旗标是否为1,为1则允许进入,为0则不准进入synchronized(同步对象){
需要被同步的代码
}
同步对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
1、同步的前提是什么
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。(即:锁使用同一个对象或者在同一个同步代码块(同步函数)里)
必须保证同步中只能有一个线程在运行。
2、同步的优劣?
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
3、同步函数的锁是什么?如果同步函数被静态修饰后,使用的锁是什么呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
如果同步函数被静态修饰后,使用的不是this。因为静态的方法是随着类的加载而执行,此时还没有对象存在,this自然也不存在。静态方法中也不可以定义this。但是静态方法执行时一定有该类对应的字节码文件对象。即:类名.class,该对象的类型是Class,所以静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class。
四、懒汉式和饿汉式
饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。
懒汉式:当程序第一次访问单件模式实例时才进行创建。
代码如下:
//饿汉式class Single
{//先建立该类对象private static final Single s = new Single();private Single(){}public static Single getInstance()
{return s;
}
}//懒汉式class Single
{//先不建立对象private static Single s = null;private Single(){}public static Single getInstance()
{//判断是否为空if(s==null)
{//添加一个同步代码块,提高安全性synchronized(Single.class)
{if(s==null)//--->A;//此时才建立对象s = new Single();
}
}return s;
}
}
两者的区别在于单例对象建立的时间不同。懒汉式的特点在于实例的延时加载,缺点是多线程访问会出现安全问题,解决办法是加上同步代码块,用双重判断的方式提高效率,同步代码块的锁是该类对应的字节码文件对象
如何选择?:如果单件模式实例在系统中经常会被用到,饿汉式是一个不错的选择。反之如果单件模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。
版权声明:本文为博主原创文章,未经博主允许不得转载。