多线程
1.程序、进程和线程
1.1程序
Java源程序和字节码文件被称为“程序” (Program),是一个静态的概念。
1.2进程
执行中的程序叫做进程(Process),是一个动态的概念。进程是程序的一次动态执行过程, 占用特定的地址空间.
进程是资源分配的最小单位 (程序),一个进程可以包含1~n个线程 ,每个进程都是独立的,都有自己的代码和运行空间,进程之间的切换会有较大的开销。
1.3线程
线程是进程中一个“单一的连续控制流程” (a single sequential flow of control)/执行路径。
线程是cpu调度的最小单位,每个线程都有自己的运行栈和程序计数器,不同的线程之间可以有某种程度上的资源共享,线程之间切换开销小,所以线程又被称为轻量级进程(lightweight process)。
线程之间可以快速切换,达到同一份程序产生好几个进程的效果。
2.多线程优缺点
优点: 资源利用率更好;程序设计在某些情况下更简单;程序响应更快。
缺点: 线程之间的交互往往非常复杂。不正确的线程同步产 生的错误非常难以被发现,并且重现以修复。
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 资源分配的单位 | 调度和执行的单位 |
开销 | 每个都有自己的代码和空间,进程之间切换有较大的开销。 | 同一类线程共享代码和空间,每个线程都有自己的运行栈和程序计数器,线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务 | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行时为每个进程分配不同的内存区域。 | 除了CPU之外不会为线程分配内存,线程使用的资源时它所属进程的资源,线程组只能共享资源。 |
包含关系 | 一个进程可以拥有多个线程,没有线程的进程可以被看作单线程。 | 线程是进程的一部分。 |
3.创建线程
3.1继承Thread ,重写run()方法,在方法内部定义线程体
缺点:如果类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承
Thread 类 ,也无法实现资源共享。
public class TheadPractice extends Thread{
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("敲代码...........");
}
}
public static void main(String[] args) {
Thread th = new TheadPractice();
th.start();//开启线程,不可以用th.run(),此为th在主线程中对run方法的调用,
for(int i=0;i<20;i++) {
System.out.println("唱歌...........");
}
//th.start();此时主线程已执行完毕,开启多线程没有意义。
}
}
3.2通过Runnable接口实现多线程
优点:可以同时实现继承,可以实现资源共享。
package com.tyl.homework07011;
public class ThreadPractice02 {
public static void main(String[] args) {
//接口多态,run()方法在Test内部,需要通过Test对象调用run方法
Runnable t = new Test();
Thread th = new Thread(t);
th.start();
for(int i=0;i<20;i++) {
System.out.println("一边笑");
try {
th.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Test implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边吃饭");
try {
Thread.sleep(400);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.3Runnable接口实现多线程资源共享案例
简单模拟12306购票,3个人重复不停地买同100张票。
package com.tyl.homework07011;
public class Case12306 implements Runnable{
//设定票数为100张,此为A、B、C的共享资源
int ticketsNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//票数为0时,线程结束
if(ticketsNum<=0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+ticketsNum--+"票");
}
}
public static void main(String[] args) {
Case12306 c = new Case12306();
Thread th1 = new Thread(c,"A");
Thread th2 = new Thread(c,"B");
Thread th3 = new Thread(c,"C");
th1.start();
th2.start();
th3.start();
}
}
由于线程不安全,运行结果会出现一张票多个人买的情况。可以通过synchronized关键字解决。
4.线程的五种状态
1)、新生状态: new
new()的时候,线程处于新生状态。
2)、就绪状态: runnable
进入就绪状态的几种情况:
- start()
- 阻塞解除
- 线程切换
- yield() 礼让线程
3)、运行状态: running
cpu将资源分配线程的时候,线程进入运行状态。
4)、阻塞状态: blocked
进入阻塞状态的几种情况
- sleep()
- wait()
- join()
5)、执行完毕: dead
进入终止状态的几种情况
- stop destroy() 不推荐使用
- 通过标识判断–推荐
- 线程正常执行完毕
6. 阻塞状态(sleep/yield/join方法)
6.1sleep方法
在sleep休眠时间过程当中,会让出cpu的资源, 当休眠解除,即恢复到就绪状态,供cpu选择
-
抱着资源睡觉,—> 对象资源(对象的锁),不会释放锁,别的线程也不可以访问锁定对象。
-
作用:
1.模拟 网络延迟
2.放大问题的可能性
案例:倒计时1~10
package com.tyl.homework07011; public class CountDown implements Runnable{ @Override public void run() { System.out.println("开始倒计时喽........"); for(int i=10;i>0;i--) { System.out.println(i); try { Thread.sleep(1000L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) { CountDown cd = new CountDown(); Thread th = new Thread(cd); th.start(); } }
6.2yield方法
让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。
package com.tyl.homework07011;
public class YieldPractice implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"开始啦");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"结束啦");
}
public static void main(String[] args) {
YieldPractice yp = new YieldPractice();
new Thread(yp,"A").start();
new Thread(yp,"B").start();
}
}
6.3join方法
使用join方法时,另一个线程需要等待调用该方法的线程执行完毕后再往下继续执行。
package com.tyl.homework07011;
public class JoinPractice implements Runnable{
public static void main(String[] args) {
Runnable r = new JoinPractice();
Thread th = new Thread(r);
for(int i=0;i<10;i++) {
System.out.println("main");
if(i==5) {
//进入就绪状态后才能执行join方法
th.start();
try {
th.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@Override
public void run() {
System.out.println("A线程开始------------------");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A线程结束------------------");
}
}
7.使用内部类、匿名内部类、Lambda开启线程
package com.tyl.homework07011;
public class LambdaTest {
//内部类
static class InnerClass implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<20;i++) {
System.out.println("内部类"+i);
}
}
public static void main(String[] args) {
Thread th1 = new Thread (new InnerClass());
th1.start();
//匿名内部类
Thread th2 = new Thread(new Runnable(){
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<20;i++) {
System.out.println("匿名内部类"+i);
}
}
});
th2.start();
//Lambda
new Thread( ()->{
for(int i=0;i<20;i++) {
System.out.println("Lambda"+i);
}
}).start();
8. synchronized同步
8.1 synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。
synchronized 方法控制对类成员变量的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该
方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得 该锁,重新进入可执行状态。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,代码范围小会出现锁不住对象的问题。
8.2 synchronized 块
通过** synchronized****关键字来声明synchronized 块。语法如下:
synchronized (sync Object)
{
//允许访问控制的代码
}
8.3用synchronized解决懒汉式单例线程不安全的问题
8.3.1同步newInstance方法
package com.tyl.homework07011;
public class SynchronizedPractice {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
}
}
class Single03{
private static Single03 single03;
private Single03() {
super();
// TODO Auto-generated constructor stub
}
public static synchronized Single03 newInstance() {
if(single03 == null) {
single03 = new Single03();
}
return single03;
}
}
8.3.2使用双重检查(double check)
package com.tyl.homework07011;
public class SynchronizedPractice {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
new Thread(()->{
System.out.println(Single03.newInstance());
}).start();
}
}
class Single03{
private static Single03 single03;
private Single03() {
super();
// TODO Auto-generated constructor stub
}
public static Single03 newInstance() {
if(single03 == null) {//外层判断:当线程1走完了内层判断,对象实例化了,线程3也调用了
//getInstace函数,如果没有外层的判断,线程3要继续等待线程2的完成,加上外层判断,就不
//需要等待了,直接返回了实例化的对象,提高效率。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//双重检查
synchronized (Single03.class){//同步类,线程1到达这里,线程2、3等待
if(single03 == null){//线程1发现single03为空,执行if语句,执行完退出后线程2进入。
//如果不加一次判断,会造成single03再次实例化,增加判断后,线程2发现已经single3
//已经被实例化直接跳过if语句
single03 = new Single03();
}
}
}
return single03;
}
}
8.4 以3.3中模拟12306抢票为案例,说明synchronized使用的几种方法
8.4.1通过同步成员方法实现线程安全
package com.tyl.homework07011;
public class Sync01Case12306 implements Runnable{
//设定票数为100张,此为A、B、C的共享资源
int ticketsNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//票数为0时,线程结束
if(Check()) {
break;
}
}
}
public synchronized boolean Check() {
if(ticketsNum<=0) {
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+ticketsNum--+"票");
return false;
}
public static void main(String[] args) {
Sync01Case12306 c = new Sync01Case12306();
Thread th1 = new Thread(c,"A");
Thread th2 = new Thread(c,"B");
Thread th3 = new Thread(c,"C");
th1.start();
th2.start();
th3.start();
}
}
8.4.2用synchronized块同步类的class对象
package com.tyl.homework07011;
/**
* 同步块------类 的Class对象, 唯一的不变的
* 如果锁类,这个类的所有对象都锁住,因为类的Class对象只有一个
* @author 86137
*
*/
public class Sync02Case12306 implements Runnable{
//设定票数为100张,此为A、B、C的共享资源
int ticketsNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//票数为0时,线程结束
if(Check()) {
break;
}
}
}
public boolean Check() {
synchronized (Sync02Case12306.class){
if(ticketsNum<=0) {
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+ticketsNum--+"票");
return false;
}
}
public static void main(String[] args) {
Sync02Case12306 c = new Sync02Case12306();
Thread th1 = new Thread(c,"A");
Thread th2 = new Thread(c,"B");
Thread th3 = new Thread(c,"C");
th1.start();
th2.start();
th3.start();
}
}
8.4.3用synchronized块同步成员方法中当前正在运行的对象
package com.tyl.homework07011;
/**
*
* 同步块---this-->在成员方法中指代当前调用方法的对象
* 如果当前对象有多个资源,都被锁住,
* @author 86137
*
*/
public class Sync03Case12306 implements Runnable{
//设定票数为100张,此为A、B、C的共享资源
int ticketsNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized (this) {
//票数为0时,线程结束
if(ticketsNum<=0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购第"+ticketsNum--+"票");
}
}
}
public static void main(String[] args) {
Sync03Case12306 c = new Sync03Case12306();
Thread th1 = new Thread(c,"A");
Thread th2 = new Thread(c,"B");
Thread th3 = new Thread(c,"C");
th1.start();
th2.start();
th3.start();
}
}
8.4.4用synchronized块同步成员变量
package com.tyl.homework07011;
/**
* 同步块---成员变量-->一定要为自定义的引用数据类型的对象地址
* @author 86137
*
*/
public class Sync04Case12306 implements Runnable{
Tickets tickets = new Tickets();
//设定票数为100张,此为A、B、C的共享资源
int ticketsNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized (tickets) {
//票数为0时,线程结束
if(ticketsNum<=0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+ticketsNum--+"票");
}
}
}
public static void main(String[] args) {
Sync04Case12306 c = new Sync04Case12306();
Thread th1 = new Thread(c,"A");
Thread th2 = new Thread(c,"B");
Thread th3 = new Thread(c,"C");
th1.start();
th2.start();
th3.start();
}
}
class Tickets{
int ticketsNum = 100;
}