学习线程先看三个概念:
进程:进程(process)正在进行中的程序,是一个静态的概念。
线程:是进程中的一个”单一的连续控制流程”。
先看一张关于线程生命周期的图(图是盗的,但想表达意思是真的【捂脸】)
4、阻塞状态(对应Blocked):正在运行的线程,由于某种特定的原因,cpu暂时放弃对它的调度,停止执行,即进入阻塞状态,直到此线程进入就绪状态才有机会再次运行。根据其阻塞的原因,可分为三种:
5、死亡状态:线程线程执行完run()方法或者因异常退出而终止,该线程就结束了生命周期。
再来个简单的结构图:
了解了线程的基本形态,那么如何创建多线程呢?
在java中,多线程的创建有三种基本的形式,由于知识有限【尴尬】,在这里只给大家介绍常用的两种基本形式:
第一种:继承Thread类,重写run()方法:
代码实例:
public class ThreadWork1 extends Thread { //继承Thread类
private int num = 10;
@Override
public void run() { //重写run()方法
// TODO Auto-generated method stub
super.run();
for (int i = 0; i < 100; i++) { //用5个线程输出10以内的偶数
if(num > 0){
num--;
if( num % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "输出" + num);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class ThreadTest1 {
//测试类,定义5个线程,并且调用.start()方法启动线程
public static void main(String[] args) {
Thread thread1 = new ThreadWork1();
Thread thread2 = new ThreadWork1();
Thread thread3 = new ThreadWork1();
Thread thread4 = new ThreadWork1();
Thread thread5 = new ThreadWork1();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
输出结果:
第二种:实现Runable接口,重写run()方法。
代码实例:
public class ThreadDemo implements Runnable{ //实现Runable接口
private int ticket = 5;
@Override
public void run() { //重写run方法,用四个线程,模拟卖票
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"我正在出售第"+(ticket--)+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class ThreadTest { //测试类,定义四个线程并给线程起名字为"窗口X",并启动线程
public static void main(String[] args) {
ThreadDemo4 t4 = new ThreadDemo4();
Thread thread1 = new Thread(t4,"窗口一:");
Thread thread2 = new Thread(t4,"窗口二: ");
Thread thread3 = new Thread(t4,"窗口三: ");
Thread thread4 = new Thread(t4,"窗口四 :");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果:
根据输出的结果发现,窗口一和窗口三都在出售第四张票,按照实际情况来说,是不会出现这样的情况的(就比如坐火车,买了同样的一张票该怎么办,到时候就必定引起矛盾了,然后。。。扯远了)所以我们就引入了线程的同步问题。线程同步问题等下再说,先看这两种创建线程的形式,继承和实现接口有什么区别呢?
不同的地方在于创建线程时的不同:
继承---> Thread thread1 = new ThreadWork1();
实现接口---> ThreadDemo4 t4 = new ThreadDemo4();
Thread thread1 = new Thread(t4);
这两种方法都可以成功创建线程,具体需要用什么形呢?如果一个类已经继承了别的父类,那就得需要用到实现Runable接口来创建线程了。
我们刚才说到模拟卖票的实例,有两个窗口同时卖了同一张票,甚至多次运行还会出现0和负数,那么遇到这种问题该如何解决呢?
这就需要引入多线程的同步(synchronized)
public void run() {
while(true){
synchronized (this) {//通常将当前对象作为同步对象
if (tick>0) {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"卖票:"+tick--);
}
}
}
}
在run()方法中使用了synchronized(this){}同步代码块,一般来说,同步代码块会放在需要同步数据的位置,也可以放在方法中,让方法实现同步。
public void run() {
while(true){
sale();
}
}
public synchronized void sale(){ //同步放在方法中
if (tick>0) {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"卖票:"+tick--);
}
}
同步监视器:
* synchronized(obj){}中的obj称为同步监视器
* 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
* 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象本身
同步监视器的执行过程:
简单的来说,同步就是让一个线程操作共享的数据,其他线程等待,该线程执行完另一个线程开始执行。
同步的前提:
同步可以保证资源共享操作的正确性,但过多的同步会导致死锁问题。
死锁一般是线程在互相等待,都没有执行。解决方法,引入了java多线程的通讯。
需要用到消费者和生产者问题:
/**
* 实体类
* @author lt
*
*/
public class Q {
private String name;
private int num;
boolean value = false;
/**
* 消费
* @return
*/
public synchronized String get() {
if(!value){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费第"+num+"瓶"+name);
System.out.println("------------");
value = false;
notify();
return num+name;
}
/**
* 生产
* @param name
* @param num
*/
public synchronized void set(String name,int num) {
if(value){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.num = num;
value = true;
System.out.println("生产第"+num+"瓶"+name);
notify();
}
}
.定义实体类,定义属性及消费方法,生产方法。
/**
* 生产者类
* @author lt
*
*/
public class Producer implements Runnable{
Q q = new Q();
Thread thread;
public Producer(Q q) {
// TODO Auto-generated constructor stub
this.q = q;
thread = new Thread(this,"生产");
thread.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
int num = 1;
while(true){
q.set("娃哈哈", num++);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
.定义生产者类,实现接口,重写方法。
/**
* 消费者类
* @author lt
*
*/
public class Consumer implements Runnable{
Q q = new Q();
Thread thread;
public Consumer(Q q) {
// TODO Auto-generated constructor stub
this.q = q;
thread = new Thread(this,"消费");
thread.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
q.get();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
.定义消费者类,同样实现接口,重写方法。
public class AppMain {
//测试类
public static void main(String[] args) {
// TODO Auto-generated method stub
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}
运行结果:
由结果发现,生产一瓶,消费一瓶,这样保证了资源的正确性,也不会存在线程死锁问题。在实体类中,定义了一个boolean类型的变量模拟死锁问题,运用wait()方法和notify()方法,让线程停止然后另一个线程执行完毕后再唤醒,有效的解决的因资源同步出现的死锁问题。
——————————————————————————————————————————
本人知识有限,如有错误或者不准确的地方,感谢指正【抱拳】。