线程
在操作系统中我们引入进程是为了使多个程序能够并发执行,以提高资源利用率,那么引入线程就是为了减少程序在并发执行所付出的时间开销
进程的基本属性
- 我们知道进程是一个可拥有独立资源的单位,一个进程要能独立运行,他必须要有一定的资源。
- 进程同时又是一个可以独立调度和分配的基本单位。
并发和并行
并发性和并行性是两个相似但有区别的两个概念
-
并发性
两个或多个事件在同一时间间隔内发生
-
并行性
两个或多个事件在同一时刻发生
那么程序并发执行需要付出的时空开销需要完成以下的操作:
- 创建进程。分配其必须的、除处理机外的所有资源
- 撤销进程。先对其占有的资源执行回收操作,然后再撤销PCB(进程控制块)
- 进程切换。对进程进行上下文切换,需要保留当前进程的CPU环境,设置新的花费处理机时间。
由于进程是一个资源的拥有者,因而在创建、撤销、和切换中,系统必须为之付出极大的系统开销,所以我们就引入了线程。
线程的基本属性
线程作为调度和分配的基本单位(可以独立执行程序)
这样可以做到拥有资源的基本单位不施以频繁的切换,从而节省开销
单线程和多线程
- 单线程:同一个时刻,只允许执行一个线程
- 多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个个迅雷进程可以下载多个文件
线程的基本使用
- 创建线程的两种方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 继承Thread类,重写run方法
public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();//启动线程,最终会执行cat的run方法
}
}
class Cat extends Thread{
@Override
public void run() {
super.run();
int count=0;
while (true) {
System.out.println("我是一只猫,快乐的星猫。"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count==8){
break;
}
}
}
}
2.实现Runnable接口,重写run方法
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能
- java提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
public class Thread02 {
public static void main(String[] args) {//代理模式
Dog dog = new Dog();
Thread thread = new Thread(dog);//不能直接使用dog.start()会报错,因为没有start方法,但thread有
thread.start();
}
}
class Dog implements Runnable{
int count=0;
@Override
public void run() {
while (true){
System.out.println("小狗汪汪叫hi..."+(++count)+Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count==10){
break;
}
}
}
}
-
继承Thread vs 实现Runnable的区别
实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承机制
多线程机制
进程在执行时先执行main()线程,当其碰到start方法时,执行另一个新的线程,这些新的线程也可以创建子线程,当所有的线程结束时,程序才会终止。
既然他最后是调用的run方法,为什么不直接调用run方法呢?
由于run方法只是一个简单地方法,并不会启动一个线程
我们来看一下源码:
public synchronized void start() {
start0();
}
start0()是本地方法,是JVM调用,底层是c/c++实现
真正实现多线程的效果是start0(),而不是run
private native void start0();
线程终止
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class ThreadExit01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
//如果希望main线程去控制t1线程的终止,必须可以修改 loop
//让 t1 退出run方法,从而终止 t1线程 -
Thread.sleep(10*1000);
t.setLoop(false);
}
}
class T extends Thread{
//设置一个控制变量
private boolean loop = true;
public boolean isLoop() {
return loop;
}
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("T运行中。。。");
}
}
}
线程中断
public class Method01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("线程一");//设置线程名称
t.setPriority(Thread.MIN_PRIORITY);
t.start();
for (int i = 0;i<5;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
}
t.interrupt();
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "吃包子~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中");
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被interrupt了");
}
}
}
}
线程插队
- yield:线程的礼让,让出cpu,让其他的线程执行,但是礼让的时间不确定,所以也不一定成功
- join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程的所有的任务。
public class Method02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i =1 ;i<=20;i++){
System.out.println("主线程吃了"+i+"个包子");
Thread.sleep(1000);
if (i==5) {
//join()
//t2.join();
//yield
Thread.yield();
}
}
}
}
class T2 extends Thread{
@Override
public void run() {
for (int i=1;i<=20;i++){
System.out.println("子线程(老大) 吃了"+i+"个包子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
public class Method04 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
myDaemonThread.setDaemon(true);//设置守护线程
myDaemonThread.start();
for (int i =1;i<=5;i++){
System.out.println("宝强在辛苦的工作");
Thread.sleep(1000);
}
}//当主线程结束时,子线程也结束
}
class MyDaemonThread extends Thread{
@Override
public void run() {
for (;;){//无限循环
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("马蓉和宋哲快乐的聊天");
}
}
}
线程的7种状态
线程同步机制
- 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,保证数据的完整性
同步的具体方法 - Synchronized
- Synchronized在方法声明中,表示整个方法为同步方法
//使用多线程模拟三个窗口同时售票100
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //会出现超卖现象
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
// SellTicket02 sellTicket01 = new SellTicket02();
// SellTicket02 sellTicket02 = new SellTicket02();
// SellTicket02 sellTicket03 = new SellTicket02();
//
// Thread thread01 = new Thread(sellTicket01);
// Thread thread02 = new Thread(sellTicket02);
// Thread thread03 = new Thread(sellTicket03);
//
// thread01.start();
// thread02.start();
// thread03.start();
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
//使用Synchronized实现线程同步
class SellTicket03 implements Runnable{
public static int ticketNum = 100;
private boolean loop = true;//控制run方法的变量
public synchronized void sell(){//同步方法,同一时刻只能有一个线程执行sell
if (ticketNum <= 0) {
System.out.println("售票结束");
loop=false;
return;
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数"+(--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//使用Thread方式(超卖)
class SellTicket01 extends Thread{
public static int ticketNum = 1000;//多个线程共享num
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
//休眠50毫秒,模拟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数"+(--ticketNum));
}
}
}
//使用Runnable(超卖)
class SellTicket02 implements Runnable{
@Override
public void run() {
while (true) {
if (SellTicket01.ticketNum <= 0) {
System.out.println("售票结束");
break;
}
//休眠50毫秒,模拟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数"+(--SellTicket01.ticketNum));
}
}
}
2.同步代码块
public synchronized void sell() {//同步方法,同一时刻只能有一个线程执行sell
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数" + (--ticketNum));
}
互斥锁
- java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,能有一个线程访问该对象
- 关键字synchronized来与对象互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象
//来自其他对象
Object object = new Object();
public /*synchronized*/ void sell() {//同步方法,同一时刻只能有一个线程执行sell
synchronized (/*this*/object) {
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数" + (--ticketNum));
}
}
- 同步方法(静态的)的锁为当前类本身
class SellTicket03 implements Runnable{
public static int ticketNum = 100;
private boolean loop = true;//控制run方法的变量
Object object = new Object();
//同步方法(静态的)的锁为当前类本身
//如果在静态方法中,实现一个同步代码块
/*
public static void m2(){
synchronized (SellTicket03.class){
System.out.println("m2");
}
* */
public synchronized static void m1(){
}
public static void m2(){
synchronized (SellTicket03.class){
System.out.println("m2");
}
}
}
线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。
public class DeadLock_{
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.start();
A.setName("A线程");
B.start();
B.setName("B线程");
}
}
class DeadLockDemo extends Thread {
static Object object1 = new Object();
static Object object2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
//当两个线程一个进入1一个进入3就发生死锁,因为会占用对方的资源
@Override
public void run() {
if (flag) {
synchronized (object1) {
{
System.out.println(Thread.currentThread().getName() + "进入1");
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "进入2");
}
}
}
} else {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "进入3");
synchronized (object1) {
{
System.out.println(Thread.currentThread().getName() + "进入4");
}
}
}
}
}
}
释放锁
- 当前线程的同步方法、同步代码执行结束。
- 当前线程在同步代码块遇到break 、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Threed.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
).getName() + “进入3”);
synchronized (object1) {
{
System.out.println(Thread.currentThread().getName() + “进入4”);
}
}
}
}
}
}
==释放锁==
1. 当前线程的同步方法、同步代码执行结束。
2. 当前线程在同步代码块遇到break 、return
3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
**不会释放锁**
1. 线程执行同步代码块或同步方法时,程序调用Threed.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁