多线程
什么是程序:一组独立功能的静态的计算机指令;
进程:向系统申请资源的独立单位
进程是一次程序动态的运行过程,它有就绪、阻塞、运行等状态
案例:开了一个QQ,就是开了一个进程,再打开迅雷,又开了一个进程,那我们在QQ上可以传输文字、语音、广告弹出,理解为一个线程,换句话理解就是线程是任务。一个进程可以管理多个线程,进程里面至少包括一条线程。通俗来讲,进程是领导,线程是员工,进程管理线程,进程本身不执行,只是为线程提供运行环境申请内存空间。
什么是线程:
线程是程序执行的一条路径,一个进程中可以包含多条线程。
多线程:
(1)一个进程里面同时执行多个任务
(2)一个进程里面至少有一个线程
(3)开启多个线程是为了同时执行多个任务,也是同时执行多部分代码。
利弊
好处:解决了多任务同时执行的问题
弊端:线程切换花费额外的资源,同时切换也是一个耗时的步骤
多线程并行和并发的区别
并行就是两个(或多个)任务同时进行
并发是指两个任务都请求运行,而处理器只能接受一个任务,就是把两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
Java程序运行原理和JVM的启动是多线程的吗?
Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
JVM的启动是多线程的吗
Ø JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
Ø main方法的代码执行的位置就是在主线程(路径)
Ø 一个进程有多个线程
Ø finalize()这个方法在子线程(垃圾回收线程)执行
如何创建线程:
(1)将类声明为Thread的子类
(2)然后这个子类重写run方法
(3)创建子类对象
(4)开启线程
一般来讲,少时的方法都不放在主线程里,
常用的线程的属性:
Name--获取线程的名字,默认为Thread-x
这种方式开启多线程,要求该类不能继承其他的父类,因为java是单继承。但我们可以用接口的方式来扩展。
第二种方法:创建一个类,实现runnable接口,实现run方法,创建线程Thread(ThreadRunnable),开启。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程");
}
}
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable,"崔浩麒");
thread.start();
}
}
一般都是使用第二种方法创建线程。
两种方式的区别:
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法
实现Runnable : 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
注意:线程的开启使用start,而不是用run,如果直接使用run,那程序的执行就和函数调用没有区别,一直在调用主线程。
多线程安全的条件:
(1)多个线程同时访问共享资源(共享数据)
(2)操作共享数据的代码有多条
解决办法:
对于不同执行的代码,我们只能保证当有一个线程执行的时候,其它线程不能进入执行。
类似公共厕所,进入之后上锁,离开的时候开锁,这就保证里面只有一个人在使用
Java中提供给我们一种“同步代码块”可以解决这个问题
好处:解决了线程安全的问题
坏处:相对降低了效率,以为每次都要去判断是否有同步锁
线程方法:
线程休眠:
Thread.sleep(毫秒), 控制当前线程休眠若干毫秒
1秒= 1000毫秒
1秒 = 1000毫秒* 1000微妙 * 1000纳秒(1000000000 )
获取线程名字和设置名字
Ø 通过Thread的getName()方法获取线程对象的名字
Ø 通过setName(String)方法可以设置线程对象的名字
Ø 通过构造函数可以传入String类型的名字
获取当前线程的对象
Ø Thread.currentThread()方法用于获取当前线程对象
Ø 在不同的方法中,获取的线程对象名称是有可能不一样的
Ø 在main中获取的是主线程对象
Ø 在子线程的run方法中获取的是子线程对象
线程优先级:
public class Test implements Runnable {
private String name;
Test(String name){
this.name = name;
}
@Override
public void run() {
for (int i =0;i<5;i++){
System.out.printf("%s,%d",name,i);
System.out.println( );
}
System.out.printf("%s结束!!!",name);
System.out.println( );
}
}
public class Demo {
public static void main(String[] args) {
Test test = new Test(" 骨灰盒");
Test test1 = new Test("总经理");
Test test2 = new Test("棺材板");
Thread thread = new Thread(test);
Thread thread1 = new Thread(test1);
Thread thread2 = new Thread(test2);
thread2.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY);
thread1.setPriority(Thread.MIN_PRIORITY);
thread.start();
thread1.start();
thread2.start();
}
}
守护线程
Ø setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
Ø 特点:男守护女,女的死,男的也不想活了
线程安全
public class Solder implements Runnable {
@Override
public void run() {
while (true) {
synchronized (String.class) {//同步枷锁
int no = TicketPool.getEmpty();
System.out.println(Thread.currentThread().getName() + "查到了" + no + "号票");
if (no == -1) {
System.out.println("无票");
break;
}
try {
Thread.sleep(( int ) (Math.random() * 50));
} catch (InterruptedException e) {
e.printStackTrace();
}
TicketPool.sold(no);
System.out.println(Thread.currentThread().getName() + "售出了" + no + "号票");
}
}
}
}
public class TicketPool {
private static int num;
private static boolean[]ticket;
static {
num = 100;
ticket = new boolean[num];
Arrays.fill(ticket,false);
}
/**
* 查票
* @return 票号-1代表无票
*/
public static int getEmpty(){
for (int i = 0;i<ticket.length;i++){
if (ticket[i]==false){
return i;
}
}
return -1;
}
/**
* 售票
* @param index
*/
public static void sold(int index){
ticket[index]=true;
}
}
public class Test {
public static void main(String[] args) {
Solder solder = new Solder();
Thread thread = new Thread(solder,"悟空");
Thread thread2 = new Thread(solder,"悟能");
Thread thread3 = new Thread(solder,"悟净");
thread.start();
thread2.start();
thread3.start();
}
}
加入线程
Ø join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
Ø join(int), 可以等待指定的毫秒之后继续
public class InsertThread implements Runnable {
private String name;
public InsertThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.printf("%s,%d\n", name, i);
}
System.out.printf("%s结束!!!\n", name);
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
InsertThread insertThread = new InsertThread("张三");
Thread thread = new Thread(insertThread);
thread.start();
for (int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName());
if (i==3){
thread.join();
}
}
}
}
Ø
线程让出
Ø yield() 让出cpu
public class Talk implements Runnable {
private String name;
Talk(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.printf("%s,%d\n", name, i);
Thread.yield();
}
System.out.printf("%s结束!!!\n", name);
}
}
public class Demo {
public static void main(String[] args) {
Talk talk = new Talk("崔好奇");
Talk talk1 = new Talk("崔好气");
Thread thread1 = new Thread(talk1);
Thread thread = new Thread(talk);
thread.start();
thread1.start();
}
}
线程优先级
Ø setPriority()设置线程的优先级
Ø 默认优先级是5,最小优先级1,最高优先级10
Ø 可以设置2,3,4
Ø Thread里面有静态常量
线程与同步
什么是同步
Ø 同步就是加锁,不让其它人访问
Ø synchronized指的就是同步的意思
什么情况下需要同步
Ø 当多线程并发, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步,否则会有线程安全问题.
同步代码块
Ø 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
Ø 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
Ø 使用同步锁时,应该尽是让锁的范围小点,才能提高性能
同步方法
Ø 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
Ø 非静态同步方法的锁是:this
Ø 静态同步方法的锁是:字节码对象(xx.class)
锁的总结
/** * 1.锁问题: * 同步中,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题 * 锁:this,代表当前对象 * 锁:如果 new 对象,就不是同一把锁 * 锁:字节码对象 String.class,内存中,只有一个字节码对象 * 开发中:一般都是this * * 2.在方法内部声明synchronized的就是 “同步代码块” * * 3.在声明方法的时候,添加 synchronized,就是同步方法 * 》如果是非静态方法,锁就是this * 》如果是静态方法,锁就当前类的字节码对象 * //TicketTask.class public static synchronized void test1(){} * * 4.同步使用的建议: * 同步加锁的时候,尽量让锁住的代码范围小一点,这样可以让其它线程等待时间少一点,性能高 * */ |
死锁
Ø 死锁就是大家都抱着锁,不释放
public class Demo { static String s1 = "筷子左"; static String s2 = "筷子右"; public static void main(String[] args) { new Thread(){ public void run() { while(true){ synchronized (s1) { System.out.println("线程A 拿到" + s1 + " 等待" + s2); synchronized (s2) { System.out.println("线程A 拿到" + s2 + " 开吃"); } } } }; }.start(); new Thread(){ public void run() { while(true){ synchronized (s2) { System.out.println("线程B 拿到" + s2 + " 等待" + s1); synchronized (s1) { System.out.println("线程B 拿到" + s1 + " 开吃"); } } } }; }.start(); } } |