线程
进程:程序运行时分配的资源,程序运行是分配的资源
线程:进程的一部分,依赖于进程而存在。
多个线程:同时可以执行多个任务(在执行的过程中它实际上是一种伪并发,cpu在一段时间执行多个线程,但是在某一个瞬间执行的是一个线程,通过程序计数器来实现切换进程时不发生错误,一个线程对应了一个私有的程序计数器)
创建线程的方式
(1)继承一个Thread类 重写run方法
public class Test1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("这里是线程执行" + i + "次");
}
}
}
/**
* 测试Thread
* @author kiosk
*/
public class TestThread {
public static void main(String[] args) {
Test1 test1 = new Test1();
test1.start();
for (int i = 0; i < 100; i++) {
System.err.println("这是主线程,执行" + i + "次");
}
}
}
有结果可以看出线程是分时间执行的
(2)前一个继承,是单继承,不是多继承,所以我们要实现一个接口(多实现),实现Runnable接口
public class Test2 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("这是Test2线程的第" + i + "次执行");
}
}
}
public class TestThread2 {
public static void main(String[] args) {
//实例化线程
Test2 test2 = new Test2();
//参数要传入一个接口的参数,那么我就来传入接口的实现类
Thread thread = new Thread(test2);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("这里是main方法的");
}
}
}
(3)创建线程的第三种方法:实现一个Callable接口,重写call方法,然后进行包装。
import java.util.concurrent.Callable;
/**创建线程的第二种方法
* 特点是可以抛出异常 可以有返回值
* @author kiosk
*/
public class Test3 implements Callable {
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("这是Test3的线程执行" + i + "次");
}
return 0;
}
}
import java.util.concurrent.FutureTask;
/**
* @author kiosk
*/
public class TestThread3 {
public static void main(String[] args) {
Test3 test3 = new Test3();
FutureTask<Object> objectFutureTask = new FutureTask<Object>(test3);
Thread thread = new Thread(objectFutureTask);
thread.start();
for (int i = 0; i < 100; i++) {
System.err.println("这是主线程的第" + i + "次");
}
}
}
线程的状态
- 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
- 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 - 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
多线程的问题
例如三个窗口同时进行售票的话,会出现同时卖出相同的票。
public class Ticket implements Runnable {
int num = 100;
public void run() {
while(true){
try {
//线程睡眠
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num > 0){
//如果某个睡眠的线程醒来的时候,但是另一个正在执行的线程在
//执行完num--之后,还没有给num赋值,这个睡醒的线程拿到的num的值就不是
//num--后的值
System.out.println(Thread.currentThread().getName()
+ "窗口卖出了" + (num--) + "号票"
);
}
}
}
}
public class TestTciket {
public static void main(String[] args) {
//三个卖票的线程
Ticket ticket = new Ticket();
Thread thread1 = new Thread(ticket,"一");
Thread thread2 = new Thread(ticket,"二");
Thread thread3 = new Thread(ticket,"三");
thread1.start();
thread2.start();
thread3.start();
}
}
那么怎么解决这样的问题呢?
答案是:使用锁机制,加上关键字Synchronized(可以理解为一个ATM,一次进一个,进去之后,就锁住了对象,只有锁被释放的情况下才能继续进去)
代码如下:
public class Ticket implements Runnable {
int num = 100;
Object object = new Object();
public void run() {
while(true){
//Object 就是这个锁
synchronized (object){
try {
//线程睡眠
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num > 0){
//如果某个睡眠的线程醒来的时候,但是另一个正在执行的线程在
//执行完num--之后,还没有给num赋值,这个睡醒的线程拿到的num的值就不是
//num--后的值
System.out.println(Thread.currentThread().getName()
+ "窗口卖出了" + (num--) + "号票"
);
}
}
}
}
}
死锁
(1)什么叫做死锁?
两个线程互相要得到对方的锁(比如一个独木桥,两个人迎面走来,互不相让,都要求对方先让路,就会造成两个人都不能过去的情况),说白了就会造成死循环,一种僵持的状态。
(2)线程死锁代码实现
public class Thread1 implements Runnable{
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public void run() {
if(flag == 1){
synchronized (o1){
flag = 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println(1 + Thread.currentThread().getName());
}
}
}
if(flag == 0){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println(2 + Thread.currentThread().getName());
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Thread1 t = new Thread1();
Thread thread1 = new Thread(t,"线程一");
Thread thread2 = new Thread(t,"线程二");
thread1.start();
thread2.start();
}
}
同步代码块
举例:
public class Test {
public static void main(String[] args) {
final Printer printer = new Printer();
new Thread(){
@Override
public void run() {
while (true) {
printer.print1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true) {
printer.print2();
}
}
}.start();
}
}
Synchornized关键字的位置
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
两个线程(一个线程打印偶数,一个线程打印奇数)
代码如下:
(1)编写一个打印的类(既可以打印奇数,又可以打印偶数)
/**
* 打印
* @author kiosk
*/
public class Print {
private int num = 0;
/**
* 打印奇数
*/
public synchronized void printOdd(){
//num为偶数的话进行等待
while (num % 2 ==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num ++;
//唤醒打印偶数线程
this.notify();
}
/**
* 打印偶数
*/
public synchronized void printEven(){
//当数字为奇数的时候,进行等待
while (num % 2 == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":" + num);
//唤醒打印偶数的线程
num--;
this.notify();
}
}
(2)新建两个线程,一个线程来打印技术,另一个线程来打印偶数
/**
* 线程1打印奇数 线程2打印偶数
* @author kiosk
*/
public class Test {
public static void main(String[] args) {
final Print print = new Print();
//创建打印奇数的线程
new Thread(new Runnable() {
public void run() {
while (true){
print.printOdd();
}
}
}).start();
//开启一个打印偶数的线程
new Thread(new Runnable() {
public void run() {
while (true){
print.printEven();
}
}
}).start();
}
}
两种实现线程的区别
(1)继承Thread类:重写run方法后,当执行start方法,线程会执行run方法(底层是由jvm在执行,在源码上是看不到的)
优点:代码简单
缺点:只能单继承。
(2)实现Runnable接口,是在Thread中传入一个参数(Runnable接口的实现类),然后判断是不是target为空(Runnable的实现类),如果不为空的话,执行实现类的run方法。优点:可以多实现 缺点:先要获取Thread的对象之后才能start()
采用匿名内部类的方式来实现线程
/**
* 采用匿名内部类的方式来实现线程
* @author kiosk
*/
public class Demo1 {
public static void main(String[] args) {
//继承方式来进行实现
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行第" + i + "次");
}
}
},"this is runnable").start();
//实现Runnable接口方式来实现
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行第" + i + "次");
}
}
}.start();
}
}