关闭

Java基础---多线程

247人阅读 评论(0) 收藏 举报
分类:


进程、线程、多线程的概念

进程:正在进行中的程序。

线程:进程中一个负责程序执行的控制单元。

P.S.

1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。

JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1.执行main函数的线程,该线程的任务代码都定义在main函数中。
2.负责垃圾回收的线程。

创建线程方式一:继承Thread类

1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread的子类对象创建线程。
4.调用start方法开启线程并调用线程的任务run方法执行。

jvm创建的主线程的任务都定义在了主函数中。而自定义的线程,它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的。所以Thread类也有对任务的描述。这个任务就是通过Thread类
中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。

class Demo extends Thread
{
	private String name;
	Demo(String name)
	{
		this.name = name;
	}
	public void run()
	{
		for(int x = 0;x<10;x++)
		{
			System.out.println(name+"...x="+x+"+...ThreadName="+Thread.currentThread().getName());
		}
	}
}

class ThreadDemo
{
	public static void main(String[] args)
	{
		Demo d1 = new Demo("旺财");
		Demo d2 = new Demo("xiaoqiang");
		d1.start();
		d2.start();
		for(int x = 0;x<10;x++)
		{
			System.out.println("...x="+x+"+...over="+Thread.currentThread().getName());
		}
	}
}

P.S.
1、可以通过Thread的getName方法获取线程的名称,名称格式:Thread-编号(从0开始)。
2、Thread在创建的时候,该Thread就已经命名了。源码如下:

创建线程方式二:实现Runnable接口

1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。

实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。


class Demo implements Runnable
{
	public void run()
	{
		show();
	}
	public void show()
	{
		for(int x=0;x<20;x++)
		{
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}
}

class ThreadDemo
{
	public static void main(String[] args)
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
	}
}

Thread类、Runnable接口内部源码关系模拟代码:

class Thread
{
	private Runnable r;
	public Thread(){}
	
	public Thread(Runnable r)
	{
		this.r = r;
	}

	public void run()
	{
		if(r!=null)
			r.run();
	}

	public void start()
	{
		run();
	}

}

class ThreadImp1 implements Runnable
{
	public void run()
	{
		System.out.println("runnable run");
	}
}

class ThreadDemo
{
	public static void main(String[] args)
	{
		ThreadImp1 ti = new ThreadImp1();
		Thread t = new Thread(ti);
		t.start();
	}
}

class SubThread extends Thread
{
	public void run()
	{
		System.out.println("haha");
	}
}

class ThreadDemo5
{
	public static void main(String[] args)
	{
		SubThread s = new SubThread();
		s.start();
	}
<span style="background-color: rgb(255, 255, 255);">}</span>
线程安全问题

线程安全问题产生的原因:

1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

线程安全问题的解决方案

思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。

class Ticket implements Runnable
{
	private int num = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			if(num>0)
			{
				synchronized(obj)
				{
					if(num>0)
					{
						System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
					}
					else
						break;
				}
			}
			else
				break;
		}
	}
}

class TicketDemo
{
	public static void main(String[] args)
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
原因分析:
上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。

利用同步代码块解决安全问题案例:
需求:储户,两个,每个都到银行存钱,每次存100,共存三次。

class Bank
{
    private int sum;
    public void add(int num)
    {
        synchronized(this)//同步代码块 //同步代码块的锁是任意对象
        {
            sum = sum+num;
            System.out.println("sum="+sum);
        }
    }
    /*public synchronized void add(int num)//同步函数 //同步函数的锁是固定的this
    {
            sum = sum+num;
            System.out.println("sum="+sum);
    }*/
}

class Cus implements Runnable
{
    private Bank b = new Bank();
    public void run()
    {
        for(int x= 0;x<3;x++)
        {
            b.add(100);
        }
    }
}

class BankDemo
{
    public static void main(String[] args)
    {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

死锁常见情景之一:同步的嵌套。

/*
死锁:常见情景之一:同步的嵌套。

*/
class Ticket implements Runnable
{
	private  int num = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{


		if(flag)
			while(true)
			{
				synchronized(obj)
				{
					show();
				}
			}
		else
			while(true)
				this.show();
	}

	public synchronized void show()
	{

		synchronized(obj)
		{
			if(num>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}
				
				System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
			}
		}
	}
}


class DeadLockDemo 
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
//		System.out.println("t:"+t);

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);

		t1.start();
		try{Thread.sleep(10);}catch(InterruptedException e){}
		t.flag = false;
		t2.start();
	}
}
死锁示例2:
class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}

	public void run()
	{
		
		if(flag)
		{
			while(true)
				synchronized(MyLock.locka)
				{
					System.out.println(Thread.currentThread().getName()+"..if   locka....");
					synchronized(MyLock.lockb)				{
						
						System.out.println(Thread.currentThread().getName()+"..if   lockb....");
					}
				}
		}
		else
		{
			while(true)			
				synchronized(MyLock.lockb)
				{
					System.out.println(Thread.currentThread().getName()+"..else  lockb....");
					synchronized(MyLock.locka)
					{
						System.out.println(Thread.currentThread().getName()+"..else   locka....");
					}
				}
		}

	}

}

class MyLock
{
	public static final Object locka = new Object();
	public static final Object lockb = new Object();
}




class DeadLockTest 
{
	public static void main(String[] args) 
	{
		Test a = new Test(true);
		Test b = new Test(false);

		Thread t1 = new Thread(a);
		Thread t2 = new Thread(b);
		t1.start();
		t2.start();
	}
}


线程间通信
线程间通信涉及的方法

多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

等待/唤醒机制涉及的方法:
1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
3. notifyAll():唤醒线程池中的所有线程。

P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在object类中。

生产者-消费者问题:

class Resource
{
	private String name;
	private String sex;
	private boolean flag = false;

	public synchronized void set(String name,String sex)
	{
		if(flag)
		{
			try
			{
				this.wait();
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}

	public synchronized void out()
	{
		if(!flag)
		{
			try
			{
				this.wait();
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		System.out.println(name+"..."+sex);
		flag = false;
		this.notify();
	}
}

class Input implements Runnable
{
	Resource r;
	Input(Resource r)
	{
		this.r = r;
	}

	public void run()
	{
		int x = 0;
		while(true)
		{
			if(x==0)
			{
				r.set("mike","男");
			}
			else
			{
				r.set("lili","女");
			}
			x=(x+1)%2;
		}
	}
}

class Output implements Runnable
{
	Resource r;
	Output(Resource r)
	{
		this.r = r;
	}

	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}

class ResourceDemo
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}


多生产-多消费者问题:

class Resource
{
    private String name;
    private int count = 1;
    private boolean flag = false;

    public synchronized void set(String name)
    {
        while(flag)
        {
            try
            {
                this.wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        this.name = name+count;
        count++;
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
        flag = true;
        this.notifyAll();
    }

    public synchronized void out()
    {
        while(!flag)
        {
            try
            {
                this.wait();
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        flag = false;
        this.notifyAll();
        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
        
    }
}

class Producer implements Runnable
{
    private Resource r;
    Producer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        int x = 0;
        while(true)
        {
            r.set("烤鸭");
        }
    }
}

class Consumer implements Runnable
{
    private Resource r;
    Consumer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}

class ProducerConsumerDemo
{
    public static void main(String[] args)
    {
        Resource r = new Resource();
        Consumer con = new Consumer(r);
        Producer pro = new Producer(r);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}


JDK1.5新特性

同步代码块就是对于锁的操作是隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
Condition接口中的await方法对应于Object中的wait方法。
Condition接口中的signal方法对应于Object中的notify方法。
Condition接口中的signalAll方法对应于Object中的notifyAll方法。

使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。

import java.util.concurrent.locks.*;

class Resource
{
	private String name;
	private int count= 1;
	private boolean flag = false;
	//创建一个锁对象
	Lock lock = new ReentrantLock();
	//通过已有的锁获取锁上的监视器对象
	Condition producer_con = lock.newCondition();
	Condition consumer_con = lock.newCondition();

	public void set(String name)
	{
		lock.lock();
		try
		{
			while(flag)
			{
				try
				{
					producer_con.await();
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			this.name = name+count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			consumer_con.signal();
		}
		finally
		{
			lock.unlock();
		}
	}

	public void out()
	{
		lock.lock();
		try
		{
			while(!flag)
			{
				try
				{
					consumer_con.await();
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			flag = false;
			producer_con.signal();
			System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
		}
		finally
		{
			lock.unlock();
		}
	}
}

class Producer implements Runnable
{
	private Resource r;
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.set("烤鸭");
		}
	}
}

class Consumer implements Runnable
{
	private Resource r;
	Consumer(Resource r)
	{
		this.r = r;
	}

	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}
class  ProducerConsumerDemo
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

停止线程

class StopThreadDemo
{
	public static void main(String[] args)
	{
		StopThread st = new StopThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		t1.start();
		t2.start();
		int num = 1;
		for(;;)
		{
			if(++num == 50)
			{
				t1.interrupt();//停止线程 会是线程run方法抛出InterruptedException异常
				t2.interrupt();
				break;
			}
			System.out.println("main..."+num);
		}
		System.out.println( "over");
	}
}

class StopThread implements Runnable
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(flag)
		{
			try
			{
				wait();
			}
			catch (InterruptedException e)
			{
				System.out.println(Thread.currentThread().getName()+"..."+e);
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"....");
		}
	}

	public void setFlag()
	{
		flag = false;
	}
}

线程类的其他方法

setDaemon()方法:

       1. 守护线程就是运行在系统后台的线程,如果JVM中只有守护线程,则JVM退出。

        2. Main主线程结束了(Non-daemon thread),如果此时正在运行的其他threads是daemon threads,JVM会使得这个threads停止,JVM也停下.如果此时正在运行的其他threads有Non-daemon threads,那么必须等所有的Non daemon线程结束了,JVM才会停下来。

        3. 必须等所有的Non-daemon线程都运行结束了,只剩下daemon的时候,JVM才会停下来,注意Main主程序是Non-daemon线程.

        4. 典型的守护线程例子是JVM中的系统资源自动回收线程, 我们所熟悉的Java垃圾回收线程就是一个典型的守护线程。

        5. setDaemon需要在start方法调用之前使用。

        6. 线程划分为用户线程和后台(daemon)进程,setDaemon将线程设置为后台进程

        7. setDaemon方法把java的线程设置为守护线程,此方法的调用必须在线程启动之前执行。

package org.ygy.exam;

public class ThreadDemo {
	
	public static void main(String[] args) throws InterruptedException {
		
		Thread t = new Thread() {
			
			public void run() {
				try {
					Thread.currentThread().sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println("thread:Hello World");
			}
		};
		
		t.setDaemon(true);//在start方法之前调用;默认是false;设为true,设置线程为守护线程,
		t.start();
		
		System.out.println("main:Hello World");
	}

}
这里的守护线程,在run()方法里面睡了1秒,所以在main方法执行完之后,JVM中只有一个守护线程,于是JVM就退出了。

所以输出为:main:hello World
jion方法

class Demo implements Runnable
{
	public void run()
	{
		for(int x= 0;x<5;x++)
		{
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}
}
class JoinDemo
{
	public static void main(String[] args)
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		try
		{
			t1.join();//t1线程要申请加入进来,运行。然后,主线程等待t1执行完毕。
			//临时加入一个线程运算时可以使用join方法。
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		t2.start();
		for(int x = 0;x<5;x++)
		{
			System.out.println(Thread.currentThread().toString()+"..."+x);
		}
	}
}

setPriority方法:
用法:
ThreadDeme td1 = new ThreadDemo();
ThreadDemo td2 = new ThreadDemo();

td1.setPriority(1);
td2.setPriority(10);

td1.start();
td2.start();

toString方法:

返回改线程的字符串形式,包括线程名称、优先级和线程组。

yield方法:

    这个方法是线程方法,当一个线程抢到执行权后,执行到yield()方法后,就会放弃执行权,其他线程就可以拿到执行权 了。
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
 
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
 
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
没有加Thread.yield()运行结果

有加Thread.yield()运行结果

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34480次
    • 积分:1406
    • 等级:
    • 排名:千里之外
    • 原创:103篇
    • 转载:12篇
    • 译文:2篇
    • 评论:2条