目录
一、线程介绍
1.程序:为了完成特定任务,用某种语言写的一组指令的集合。(就是写的代码。)
2.进程:运行中的程序。比如开启一个qq,微信。每开启一个进程,操作系统就会为该进程分配内存空间。
进程是程序的一次执行过程,或是正在运行的一个程序。是个动态的过程,有自己的产生、存在和消亡的过程。
3.线程:可以由进程创建,也可以由线程创建。是进程的一个实体。一个进程可以拥有多个线程。(如qq同时打开多个聊天框、迅雷同时下载多个东西。)
4.单线程和多线程:
单线程:同一时刻只允许运行一个线程。
多线程:同一时刻可以运行多个线程。
5.并发和并行:
并发:同一时刻,多个任务交替执行。(单核cpu实现的多任务就是并发。)
并行:同一时刻,多个任务同时执行。(多核cpu可以实现并行。)
二、线程的基本使用
1.创建线程的两种方式
(1)继承Thread类,重写run方法。
(2)实现Runnable接口,重写run方法。
如:
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Cat cat = new Cat();
// cat.run();
cat.start();
for (int i = 0; i < 30; i++) {
System.out.println("主线程i="+i);
Thread.sleep(1000);
}
}
}
//继承Thread类,重写run方法。
class Cat extends Thread{
int times=0;
@Override
public void run() {
while (true) {
if (times == 50){
break;
}
System.out.println("喵喵喵,我是一个小狗~"+(++times)+"线程名="+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
当子线程启动后,主线程并不会陷入阻塞,仍会继续执行。
即当子线程启动后,若是单核cpu,子线程和主线程就是交替执行的。若是多核cpu,两者就是同时执行的。
主线程和子线程还可以再开线程。
2.两种方式具体实现
public class Thread03 {
public static void main(String[] args) {
A a = new A();
a.start();
B b = new B();
Thread thread = new Thread(b);
thread.start();
}
}
class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("hi");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.两种方式的区别
(1)两者创建线程本质上没有区别。
(2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。
三、线程终止
基本说明:
(1)线程完成任务后,自然会自动退出。
(2)通知方式,即使用变量来控制run方法退出的方式停止线程。
四、线程常用方法
1.常用方法
sleep():是线程睡眠。
interrupt:中断正在休眠的线程。
yield():礼让,有可能不成功。
join():插入,强制执行插入线程。
线程T执行了yield方法,如果资源不紧张,cpu认为没有必要让出资源,就会不成功。
2.用户线程和守护线程
(1)用户线程(也叫工作线程):当线程的任务执行完或通知方式结束。
(2)守护线程:一般是为工作线程服务的,当所有的用户线程结束后,守护线程就自动结束了。
(3)常见的守护线程:垃圾回收机制。
五、线程同步机制
(1)线程同步机制
1.对一些敏感数据,或区域,保证在任意同一时刻,最多有一个线程访问,保证数据的完整性。
2.也可以理解为:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,知道该线程完成操作,其他线程才能对该内存地址进行操作。
(2)同步具体方法-synchronized
1.同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码。
//需要被同步的代码
}
2.同步方法
synchronized放在整个方法中,表示整个方法为同步方法。
public synchronized void m(String name){
//需要被同步的代码。
}
一般范围越小越好。
(3)售票问题
使用多线程,模拟3个窗口同时售票100张。
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //用3个Thread同时开启了3个线程(3个类调用了3个方法。)
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
System.out.println("====使用实现接口方式来售票====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
}
}
@SuppressWarnings({"all"})
class SellTicket01 extends Thread{
private static int ticketNum=100;
@Override
public void run() {
while (true){
if(ticketNum<=0){
System.out.println("售票结束。。");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"+
"剩余票数="+(--ticketNum));
}
}
}
@SuppressWarnings({"all"})
class SellTicket02 implements Runnable{
private int ticketNum=100;
@Override
public void run() {
while (true){
if(ticketNum<=0){
System.out.println("售票结束。。");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"+
"剩余票数="+(--ticketNum));
}
}
}
会出现超卖现象。
(4)使用synchronized解决售票问题。
@SuppressWarnings({"all"})
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //用3个Thread同时开启了3个线程(3个类调用了3个方法。)
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
// System.out.println("====使用实现接口方式来售票====");
// SellTicket02 sellTicket02 = new SellTicket02();
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
@SuppressWarnings({"all"})
class SellTicket01 extends Thread {
private static int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束。。");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" +
"剩余票数=" + (--ticketNum));
}
}
}
@SuppressWarnings({"all"})
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束。。");
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" +
"剩余票数=" + (--ticketNum));
}
}
}
@SuppressWarnings({"all"})
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
Object obj = new Object();
public /*synchronized*/ void sell() {
synchronized (/*this*/obj) {
if (ticketNum <= 0) {
System.out.println("售票结束。。");
loop = false;
return;//break是结束while循环,return是结束这个函数。
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" +
"剩余票数=" + (--ticketNum));
}
}
@Override
public void run() {
while (loop)
sell();
}
}
哪个对象执行,谁就先持有这个锁。
synchronized在静态方法中的使用:
public synchronized static void m1(){
}
public static void m2(){
synchronized (SellTicket.class){
System.out.println("m2");
}
}
六、线程的七大状态
Runnable又可细化为2个状态,Ready:就绪态和Running:运行态。
Runnable状态只是代表可以运行,但是并不代表正在运行。
当线程被挂起时,就由Running转变为Ready。是在内核发生的转变。
当使用Thread.yield(礼让)时就会Running转变成Ready(内核之中),这就是为什么yield可能不会成功。
当在主线程中使用t.join()后,对于主线程就处于一个waiting状态,等待线程t执行完。
Wait,join,park():waiting
Sleep,eait(),join(),park(),parkNanos()设计到时间就是TimedWaiting。
Blocked:线程等待锁就成了blocked状态,当获得锁后就重新称为Runnable状态。
TimeWaiting后涉及时间等待时间结束就可以了。
Waiting则需要notify(),notifyAll,unpark等方法重新进入到Runnable状态。
进入到Runnable(可运行状态)可能是Ready,也可能是Running。是否真的运行还要取决于内核的调度器。
七、线程互斥锁
1.基本介绍
1.每个对象都有一个被称为“互斥锁”的标记,这个标记保证在任意时刻,只能有一个线程来访问该对象。
2.关键字synchronized与对象互斥锁联系。当某对象用synchronized修饰时,表明该对象在任意时刻只能有一个线程来访问。
3.同步的局限性:导致程序的执行效率要降低。
4.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一对象)。
同一把锁对应同一个对象。
5.同步方法(静态的)的锁为当前类本身。类.class。
2.注意事项和细节
(1)非静态同步方法,默认锁对象为this。
(2)静态同步方法,默认锁对象为:当前类.class。
(3)实现步骤:
1.分析要上锁的代码。
2.选择使用同步代码块或同步方法。
3.要求多个线程的锁对象为同一个。
八、线程死锁
多个线程都占用了对方的锁资源,但不肯想让,导致了死锁。