java——多线程详解

实现多线程

1. 进程与线程

进程:是正在运行的程序

  • 是进程进行资源分配和调度的基本单位
  • 每个进程都有它自己的内存空间和系统资源

线程:是进程中的单个控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径的话,称为单线程
  • 多线程:一个进程如果有多条执行路径,称为多线程

2.多线程的实现方式

方式一:继承Thread类

  • 定义一个类MyThread继承Thread
  • 在MyThread中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

代码示例
MyThreadDemo.java

package THREAD;

public class MyThreadDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread th1=new MyThread();
		MyThread th2=new MyThread();
		th1.start();
		th2.start();
	}

}

MyThread

package THREAD;

public class MyThread extends Thread {
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println(i);
		}
	}
}

java——多线程详解_第1张图片

Thread类常用方法

long getId() 返回此线程的标识符。
String getName() 返回此线程的名称。
int getPriority() 返回此线程的优先级。
void setName(String name) 改变该线程的名称等于参数 name。
void setPriority(int newPriority) 更改此线程的优先级。
static void sleep(long millis) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。
static void sleep(long millis, int nanos) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数加指定数纳秒,受制于系统的高精度定时器和调度的准确性。
void start() 导致该线程开始执行;java虚拟机调用这个线程的 run方法。
static void yield()给调度程序的一个提示,当前线程愿意得到它当前的处理器的使用
void join(long millis, int nanos)等待最多 millis毫秒加上 nanos纳秒这个线程死亡
boolean isDaemon()测试这个线程是否是守护线程
void interrupt()中断这个线程
void setDaemon(boolean on)将此线程标记为 daemon线程或用户线程
static Thread currentThread()返回对当前正在执行的线程对象的引用

进程控制

一、sleep代码示例

ThreadSleep

package THREAD;

public class ThreadSleep extends Thread{
		public void run()
		{
			for(int i=0;i<5;i++) {
				System.out.println(getName()+","+i);
			}
		}
}

ThreadSleepDemo

package THREAD;

public class ThreadSleepDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadSleep t1=new ThreadSleep();
		ThreadSleep t2=new ThreadSleep();
		ThreadSleep t3=new ThreadSleep();
		
		t1.setName("java");
		t2.setName("Python");
		t3.setName("C++");
		
		t1.start();
		t2.start();
		t3.start();
	}

}

结果
java——多线程详解_第2张图片
我们发现很乱,如果想要每个线程都交替执行,就可以用sleep来控制,修改ThreadSleep即可

package THREAD;

public class ThreadSleep extends Thread{
		public void run()
		{
			for(int i=0;i<5;i++) {
				System.out.println(getName()+","+i);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
}

结果
java——多线程详解_第3张图片
如果想要让java线程运行结束,其他两个线程再运行可以用join()实现

二、join示例

ThreadJoin

package THREAD;

public class ThreadJoin extends Thread{

	public void run()
	{
		for(int i=0;i<5;i++) {
			System.out.println(getName()+","+i);
		}
	}
	
}

ThreadJoinDemo

package THREAD;

public class ThreadJoinDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadJoin t1=new ThreadJoin();
		ThreadJoin t2=new ThreadJoin();
		ThreadJoin t3=new ThreadJoin();
		
		t1.setName("Java");
		t2.setName("Python");
		t3.setName("C++");
		
		
		t1.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
		t3.start();
	}

}

结果:

java——多线程详解_第4张图片

三、setDaemon()方法示例

补充: java里有两类线程:用户线程和守护线程,所有守护线程都是为用户线程工作的,只要还有一个用户线程在运行,守护线程就必须运行,当运行的进程都是守护进程时,java虚拟机将退出。

如果我们希望java进程执行结束之后,Python进程和C++进程不再执行了,那就可以将Python进程和C++进程设置为守护线程
ThreadDaemon

package THREAD;

public class ThreadDaemon extends Thread{
	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println(getName()+","+i);
		}
	}
}

ThreadDaemonDemo

package THREAD;

public class ThreadDaemonDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadDaemon t1=new ThreadDaemon();
		ThreadDaemon t2=new ThreadDaemon();
		
		t1.setName("Python");
		t2.setName("C++");
		
		//设置主线程
		Thread.currentThread().setName("java");
		
		//设置守护线程
		t1.setDaemon(true);
		t2.setDaemon(true);
		
		t1.start();
		t2.start();
		
		for(int i=0;i<2;i++) {
			System.out.println(Thread.currentThread().getName()+","+i);
		}
	}

}

结果
java——多线程详解_第5张图片

方式二:实现Runnable接口

  • 定义一个MyRunnable类实现Runnable接口
  • 在MyRunnable类中实现重写Run方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,将MyRunnable类的对象作为构造方法的参数
  • 启动线程

代码示例:

package THREAD;

public class MyRunnableDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyRunnable mb=new MyRunnable();
		
		//Thread(Runnable target, String name) 分配一个新的 Thread对象。
		Thread th=new Thread(mb,"java");
		
		th.start();
	}

}

实现Runnable接口的好处

  1. 避免了java单继承的局限性
  2. 适合多个相同的代码去处理同一个资源的情况,把线程和程序的代码。数据有效分离,较好的体现了面向对象的思想

3.线程的生命周期

java——多线程详解_第6张图片

4.同步代码块解决数据安全问题

举个栗子
假设A和B家里有一个密码箱,里面放了五万现金,他们都有一把开这个箱子的锁,有一天A想要那这五万里的三万去买股票,但怕B知道,他就偷偷去了,所以箱子里还有两万,但B以为里面还是五万,他刚好想拿这五万去装修小屋,便去保险箱去五万,我们当然知道不可能去五万了,但是B不知道,所以这会造成B拿走前之后,保险箱里是负三万,这显然是不合法的!!
在多线程里也是一样,当几个线程共享一个资源的时候,他们会竞争该资源,但彼此永远不知道在自己访问之前,资源还有多少个,每一个线程都可以看成一把锁,他们只知道自己上一次开锁资源的剩余量,并不知道在之后有没有线程使用资源,因为别的线程也有锁,可以打开资源
如何解决该问题?那就是让资源每次只有一个线程访问呗,也就是只有一钥匙,当一个线程拿到钥匙之后,其他线程只能等到该线程是使用完才可以使用,也就是将使用资源那部分代码锁起来,并给所有线程只配备一把锁

将同步代码块锁起来就要synchronized关键字

private Object obj=new Object(); 
synchronized(obj){
   ...//同步代码块
}

代码示例:
以买票为例,假设有20张票(资源),有三个销售员(三个线程)一起卖这二十张票
SellTicket

package THREAD;

public class SellTicket implements Runnable{
	private int tickets=20;
	private Object obj=new Object();//一把锁

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			synchronized(obj) {
			if(tickets>0) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					tickets--;
					System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
				}
			}
		}
	}
	
}

SellTicketDemo

package THREAD;

public class SellTicketsDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SellTicket st=new SellTicket();
		Thread th1=new Thread(st,"1");
		Thread th2=new Thread(st,"2");
		Thread th3=new Thread(st,"3");
		
		th1.start();
		th2.start();
		th3.start();
	}

}

如果不加同步代码块的锁,我们看看执行情况:
java——多线程详解_第7张图片
可以发现,多个销售员在出售同一张票,这显然不合理,而且居然有负数个票
加锁之后
java——多线程详解_第8张图片

5.同步方法解决数据安全问题

同步方法:就是把synchronized关键字加到方法上
java——多线程详解_第9张图片

你可能感兴趣的:(java学习,java,安全)