线程与进程
-
进程是指运行中的程序
-
进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
解释:电脑打开一个程序,程序一运行就是进程,进程会占用内存空间,关闭程序。内存释放
什么是线程
-
线程时有进程创建的,是进程的一个实体
-
一个进程可以拥有多个线程
解释:打开迅雷是一个进程,迅雷同时下载多个资源,为多个线程
什么是单线程和多线程
-
单线程:同一时刻,只允许执行一个线程
-
解释:迅雷同时下载多个资源,为多个线程—多线程
什么是并发和并行
-
并发:同一时刻,多个任务交替执行。也就是单核CPU实现的多任务就是并发
-
并行:同一时刻,多个任务同时执行,多核CPU可以实现并行
解释
-
并发:一个CPU来回执行多个程序
-
并行:两个CPU同时各自实现自己的一个程序
-
并发和并行同时存在:两个CPU同时各自实现自己的多个程序
扩展:
获取自己电脑上有几个CPU
Runtime runtime = Runtime.getRuntime();
int cpunums = runtime.availableProcessors();
创建线程
-
继承Thread类,重写run方法
-
实现Runnable接口,重写run()方法
入门案例1—继承Thread类
要求:1. 编写程序,开启一个线程,该线程每个1秒。在控制台输出“喵喵,我是小猫咪”,当输出80次后,结束该进程
package threaduse;
//通过继承Thread类,创建线程
public class Thread01 {
public static void main(String[] args) {
cat cat = new cat();
cat.start();
}
}
class cat extends Thread {
多线程机制
-
执行代码:开启进程
-
执行main方法:开启main线程
-
main方法执行线程类的start方法:开启线程类的线程
-
main线程启动了线程类,不会发生阻塞,会和线程类的线程交替执行
需知点
-
main线程会和线程类线程同时运行
-
main线程可以继续开其他线程,线程类也可以开其他线程
-
main线程结束、线程类线程不会结束
-
进程中的全部线程结束,进程才会消失
为什么调用是start方法,而不是run方法
-
调用start方法是启动线程—》最终会调用线程类的run方法
-
直接调用run方法的话,run方法其实就是一个普通的方法,没有真正的启动线程,会把run方法执行完,才会先下执行
-
真正实现多线程的效果是start0方法,而不run方法
步骤是:
-
start方法
-
start0方法 start0()是本地方法,由JVM调用
-
实际上是:在start0方法里面用多线程的机制来调用run方法
入门案例—实现Runnable接口
说明:java是单继承的,在某些情况下,一个继承了某个父类,就不能再继承Thread类,这就用到了接口
需求:请编写程序,该程序可以每隔1秒,在控制台输出hi,输出10次后,自动退出,用Runnable接口方式实现
package threaduse;
public class Runnable01 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
需知点:
-
因为Dog类没办法调用start方法
-
所以创建了Thread类,把dog对象放入Thread对象中,就可以调用run方法了,因为这里底层使用设计模式【代理模式】
代码模拟 了解代理模式
解释为啥可以把dog对象放入Thread对象中,就可以调用run方法了
package threaduse;
public class Runnable01 {
public static void main(String[] args)
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();//1.
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
入门案例—多线程执行
需求:编写一个程序,创建两个线程,一个线程每隔1秒输出“HelloWorld”,输出10次,退出。一个线程每隔1秒输出“Hi”,输出5次退出
package threaduse;
public class Thread02 {
public static void main(String[] args) {
T t = new T();
TT tt = new TT();
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(tt);
thread1.start();
thread2.start();
}
}
class T implements Runnable{
线程终止
-
当线程完成任务后,会自动退出
-
通过使用变量来控制run方法退出的方式停止线程,即通知方式
package threaduse;
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
Thread thread = new Thread(t1);
thread.start();
Thread.sleep(10*1000);
//如果希望main线程去控制t1 线程的终止,必须修改a的值
t1.setA(false);
}
}
class T1 implements Runnable{
int b = 0;
private boolean a = true;
线程常用方法
-
常用方法1
-
setName 设置线程名称,使之与参数的name相同
-
getName 返回线程的名称
-
start 使线程开始执行;java虚拟机底层调用该线程的start0方法
-
run 调用线程对象run方法
-
setPriority 更改线程的优先级
-
getPriority 获取线程的优先级
-
sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
-
interrupt 中断线程
-
-
常用方法1注意点
-
start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程
-
线程的优先级范围 MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5
-
interrupt,中断线程,但并没有真正的结束线程。一般用于中断正在休眠的线程
-
sleep:线程的静态方法,使当前线程休眠
-
-
常用方法1代码实现
package threaduse;
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.setName("常世超");
t2.setPriority(Thread.MIN_PRIORITY);
t2.start();//启动子线程
//让主线程打印5次,中断子线程的休眠
for (int i = 0; i <5 ; i++) {
Thread.sleep(1000);
System.out.println("hi"+i);
}
t2.getPriority();
t2.interrupt();//当执行到这里 就会中断t2线程的休眠
}
}
class T2 extends Thread{
-
常用方法2
-
yield :线程的礼让,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
-
join :线程插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务
-
-
常用方法2代码实现
package threaduse;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
Thread thread = new Thread(t3);
thread.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("main正在进行第" + i + "次冲刺");
if (i == 10) {
thread.yield();//线程的礼让
// thread.join();//线程插队
}
}
}
}
class T3 implements Runnable {
-
常用方法3
-
用户线程:也叫工作线程,当线程的任务执行完或者通知方式结束
-
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
-
-
代码实现
package threaduse;
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
A2 a2 = new A2();
a2.setDaemon(true);
a2.start();
for (int i = 1; i <=10 ; i++) {
System.out.println("我是用户线程"+i);
Thread.sleep(1000);
}
}
}
class A2 extends Thread{
@Override
public void run() {
for ( ; ;) {
System.out.println("我是守护线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常用方法练习题
package threaduse;
/*1. 主线程每个1秒,输出hi,一共10次
* 2.当输出到 hi 5时 ,启动一个子线程(要求实现Runnable接口),每个1s输出hello,等该线程输出10次后,退出
* 3.主线程继续输出hi,直到线程结束*/
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread = new Thread(a);
for (int i = 1;i<=10;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
if(i==5){
thread.start();
thread.join();
}
}
}
}
class A implements Runnable{
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println("hello"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的生命周期
-
-
-
Ready—-就绪状态
-
Running—-运行状态
-
-
-
-
-
售票案例引出线程同步锁
出现的问题:
-
问题1.重卖:一张票卖给了多个人
-
问题2.超卖:出现了票数为0甚至是负数的情况
package threaduse;
public class Mai {
public static void main(String[] args) {
Csc csc = new Csc();
Thread thread1 = new Thread(csc);
Thread thread2 = new Thread(csc);
Thread thread3 = new Thread(csc);
thread1.start();
thread2.start();
thread3.start();
}
}
class Csc implements Runnable{
int a = 100;
@Override
public void run() {
while (true){
if(a<=0) {
System.out.println("售票结束");
break;
}
try {
//让程序休眠后出现的两个问题:
//问题1.重卖:一张票卖给了多个人
//问题2.超卖:出现了票数为0甚至是负数的情况
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"\t"+(--a));
}
}
}
同步锁
-
判断程序有没有出现线程安全问题:
在多线程程序中 + 有共享数据 + 多条语句操作共享数据
-
同步与异步:
-
同步:体现了排队的效果,同一时刻只能有一个线程占用资源,其他没有权限的线程排队
坏处:效率低,
好处:安全
-
异步:体现了多线程抢占资源的效果,线程间互想不等待,互相抢占资源
坏处:有安全隐患
好处:效率高
-
-
同步锁关键字:synchronized
-
实现同步具体方法
-
同步代码块
synchronized (锁对象){//使用的锁对象类型任意,但注意:必须唯一!
//需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
} -
同步方法
public synchronized void m(){
//需要同步的代码
}
-
-
使用同步锁的前提
-
同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
-
多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
-
-
锁对象
-
(非静态的)同步方法的锁可以是this,也可以是其他对象(要求是同一对象)
public synchronized void aVoid(){
System.out.println("我爱你");
}
//解释(非静态的)同步方法的锁可以是this,也可以是其他对象(要求是同一对象)
public void a(){
Object o = new Object();
synchronized (this){
// synchronized (o){
System.out.println("我爱你");
}
} -
(静态的)同步方法的锁对象是当前类本身
public static synchronized void a(){
System.out.println("我爱你");
}
//解释静态的同步方法锁对象时类本身
public static void a(){
synchronized (Csc.class){
System.out.println("我爱你");
}
} -
同步代码块的锁:
@Override
public void run() {
Object o = new Object();
synchronized (o) {}
}
@Override
public void run() {
synchronized (this) {}
}
-
-
线程的死锁
-
介绍
多个线程都占用了对方的锁资源、但不肯想让,导致了死锁,在编程是一定要避免死锁的发生
-
应用案例
妈妈:你先写作业,我让你玩手机
儿子:你先让我玩手机,我就写作业
-
代码实现
package threaduse;
public class DeadLock {
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.setName("线程A");
B.setName("线程B");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag ;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
/*下面有业务逻辑
* 1.如果flag 为T ,线程A 就会先得到/持有 o1 对象锁,然后尝试去获取o2 对象锁
* 2.如果线程A 得不到 o2 对象锁,就会Blocked
* 3.如果flag 为F , 线程B就会就会先得到/持有 o2 对象锁,然后尝试去获取o1 对象锁
* 4.如果线程B 得不到 o1 对象锁,就会Blocked*/
if(flag) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"进入状态1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"进入状态2");
}
}
}else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"进入状态3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"进入状态4");
}
}
}
}
}
释放锁
-
下面操作会释放锁
-
当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
-
当前线程在同步方法、同步代码块中遇到break、return。
案例:没有正常完事,经理叫他修BUG,不得不出来
-
当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致异常结束
案例:没有正常完事,发现为带纸,不得不出来
-
当前线程在同步方法、同步代码块中执行了wait()方法,当前线程暂停,并释放锁
案例:没有正常完事,觉得需要酝酿一下,所以出来等会再进去
-
-
下面操作不会释放锁
-
线程执行同步方法、代码块时,程序调用了Thread.sleep()、Thread.yield()方法暂停当前线程的执行、不会释放锁
案例:上厕所,眯一会
-
线程执行同步方法、代码块时,其他线程调用了suspend()方法将线程挂起,该线程不会释放锁
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
-
-