第一部分:线程,进程的概念
1.代码执行的路径只有一条我们称之为单线程。
代码执行有多条路径执行的我们称之为多线程。
2.进程:线程要依赖于进程。进程就是正在执行的程序。
线程:当进程开启之后要执行很多任务,每一个要执行的任务我们称之为线程。
多进程的意义:提高CPU的利用率。
单核CPU在某个时间点上,只能执行一个进程。
多线程的意义:提高程序的使用率。
3.并行:是逻辑上的同时发生,某一个时间段内同时发生;
并发:是物理上的同时发生,某一个时间点同时发生。
4.进程是拥有资源的基本单位;线程是CUP调度的基本单位。
5.Java如何实现多线程?
因为线程是依赖于进程存在的,java要创建进程,需要依赖于系统功能创建,但是java 是不能直接调用系统功能的。但是C、C++是可以的,所以java去调用C、C++编写的类进而去创建进程。所以Java提供了一个类 Thread,通过这个类实现多线程。
6.创 建新执行线程有两种方法:
(一)、第一种方法:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法;
步骤:(1).写一个类,继承Thread类,(2)重写Thread类中的run 方法,(3)开启线程start()方法。
注意事项:
1.Run方法里面的代码是线程要执行的代码。
2.一般来说,一些耗时的操作需要我们开启线程在run中操作。
3.重复开启线程会报错,再开启一个线程就要再new一个新的对象。
4.多个线程执行具有随机性,都在抢占CPU的执行权(时间片),哪个线程抢到了CPU的执行权,那CPU就执行谁。
代码实现:
public class MyThread1 extends Thread {
//创建线程的第一种方法
String name;
@Override
public void run(){
//比较耗时的代码块(即线程要执行的代码块)
name=this.getName();//获取线程的名字
for(int i=0;i<100;i++){
System.out.println(name+"----"+i);
}
}
}
public class MyThread1Test {
public static void main(String[] args) {
Thread th1 = new MyThread1();//创建线程对象
Thread th2=new MyThread1();
th2.start();
th1.start();//启动线程
}
}
Thread类中的一些方法:
(1)获取线程的名字:public final String getName();
(2)设置线程的名称:public final void setName(String name);
(3)获取当前线程:public static Thread curretThread();
(4)通过构造方法设置线程名字Thread(String name)。
代码实现:
public class MyThread2 extends Thread{
String name;
public MyThread2(){}
public MyThread2(String name){
this.name=name;
}
@Override
public void run() {
name=this.getName();
for(int i=0;i<10;i++){
System.out.println(name+"--"+i);
}
}
}
public class MyThread2Test {
public static void main(String[] args) {
Thread th1 = new MyThread2();//创建线程对象
Thread th2=new MyThread2();
Thread th3=new MyThread2("线程3");
Thread thread=Thread.currentThread();
String name=thread.getName();
System.out.println(name);//main
th1.setName("线程1");//给线程命名
th2.setName("线程2");
th2.start();
th1.start();//启动线程
th3.start();
}
}
(5)CPU的调度模型:1.分时调度模型,即所有线程轮流使用CPU,平均分配CPU给线程。2.抢占式调度模型有优先级,优先级高的线程获取的时间片相对较多。
Java采用的是抢占式调度模式:
如何设置优先级:public final void setPriority(int newPriority);
获取线程优先级:public final int getPriority();线程默认优先级是5;
最高优先级MAX_PRIORITY是10
最低优先级MIN_PRIORITY是1
设置优先级时,最好是不要直接写数字,要定义一个常量,方便更改;
注意:有时候给线程设置了优先级,但是执行时并不一定是按设置好的优先级进行的,优先级高仅仅是代表这个线程被CPU执行的概率增大了,多线程执行具有随机性,所以最好多运行几次看结果;
(6)线程休眠:public static void sleep(long millis);millis为休眠的毫秒值;
(7)线程加入:该线程执行完毕其他线程才能执行。
join()方法;public final void join();
注意:要在线程启动之后才调用join()方法设置为加入线程。
代码演示:
public class MyThreadTest3 {
public static void main(String[] args) throws InterruptedException{
MyThread1 th1= new MyThread1();
MyThread1 th2=new MyThread1();
MyThread1 th3=new MyThread1();
th1.setName("线程1");
th2.setName("线程2");
th3.setName("线程3");
int prio=th1.getPriority();
System.out.println(prio);//默认优先级为5
th1.setPriority(Thread.MAX_PRIORITY);//设置成最大优先级10
th2.setPriority(Thread.MIN_PRIORITY);//设置成最小优先级1
th3.start();//启动线程3
th3.join();//把线程3设置为加入线程,即线程3执行完毕才执行其他线程。
th1.start();
th2.start();
}
}
(8)线程礼让:public static void yield();暂停当前正在执行的线程,并执行其它的线程。
理想中就是你执行一下,我执行一 下,交替执行,但是效果不明显。因为暂停的时间比较短暂,之后又开始抢占CPU的执行权,那么这个线程将和其它线程一起抢占,所以有可能接下来还是这个线程执行。
代码演示:
public class ThreadTest4 {
public static void main(String[] args) {
MyThread1 th1 = new MyThread1();
MyThread1 th2 = new MyThread1();
th1.setName("线程1");
th2.setName("线程2");
Thread.yield();//线程礼让
th1.start();
th2.start();
}
}
(9)守护线程:public final void setDaemon(boolean on);所谓的守护线程就是当所守护的线程执行完毕之后,守护线程本身就死掉。就好比象棋一样,守护的将死了,则全盘就结束了,小兵随之全死。如下例子:张飞关羽设置成守护线程,守护刘备,为 了效果更明显,把刘备设置成加入线程,刘备执行完后则程序全部结束,张飞关羽线程并没有执行则随刘备的结束而结束。
程序代码:
public class MyThreadTest5 {
public static void main(String[] args) {
MyThread1 th1 = new MyThread1();
MyThread1 th2 = new MyThread1();
MyThread1 th3 = new MyThread1();
th1.setName("张飞");
th2.setName("关羽");
th3.setName("刘备");
th1.setDaemon(true);
th2.setDaemon(true);
th3.start();
try {
th3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
th1.start();
th2.start();
}
}
(10)线程中断停止运行:public final void stop();
打断线程的阻塞状态 public void interrupt();
线程休眠属于线程的阻塞状态,sleep(long time),wait()方法是阻塞状态;
代码演示:
public class MyThreadTest6 {
public static void main(String[] args) throws InterruptedException {
MyThread1 th1 = new MyThread1();
th1.setName("线程1");
th1.start();
// th1.stop();//直接停止运行
th1.sleep(5000);
th1.interrupt();//打破睡眠的5秒钟,直接运行。
}
}
(二)、创建新执行线程有的第二种方法:
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
这种方式比较灵活,实现一个接口的同时还可以继承其他的类。所以可多采用第二种方法。
代码实现:
public class MyRunnable implements Runnable{
String name;
@Override
public void run() {
for(int i=0;i<100;i++){
name=Thread.currentThread().getName();//采用当前线程获得名字
System.out.println(name+"---"+i);
}
}
}
public class MyRunnableTest1 {
public static void main(String[] args) {
MyRunnable myRunnable1= new MyRunnable();
MyRunnable myRunnable2=new MyRunnable();
Thread th1 = new Thread(myRunnable1);
Thread th2=new Thread(myRunnable2);
th1.setName("线程1");
th2.setName("线程2");
th1.start();
th2.start();
}
}
第二部分:线程安全方面知识
1.案例演示:
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。通过继承Thread类实现。
案例分析:3个售票窗口售票是同时进行的,故需要三个线程来控制三个窗口,三个窗口一共有100张票,即100为共享数据。程序代码如下:
public class MyThread1 extends Thread {
private String name;
public MyThread1(String name){
this.name=name;
}
private static int tickets=100;//定义成共享数据
@Override
public void run() {
while(true){
if(tickets>0){
System.out.println(this.name+"正在售票第"+tickets--+"张票");
}
else{
break;
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread1 th1 = new MyThread1("窗口1");
MyThread1 th2 = new MyThread1("窗口2");
MyThread1 th3 = new MyThread1("窗口3");
th1.start();
th2.start();
th3.start();
}
}
案例需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。通过实现Runnable接口实现。代码如下:
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
private static int tickets = 100;//定义成共享数据
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
} else {
break;
}
}
}
}
public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable1=new MyRunnable();
MyRunnable myRunnable2=new MyRunnable();
MyRunnable myRunnable3=new MyRunnable();
Thread th1=new Thread(myRunnable1);
Thread th2=new Thread(myRunnable2);
Thread th3=new Thread(myRunnable3);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
但是在实际问题中所售两张票之间有延迟,在上述代码中加入延迟时间500毫秒,则会有相同的票数出现,或者零票负票出现,这些均不合情理,即出现了数据安全的问题。
2.出现数据安全问题的三个条件:(1).多线程环境(2).要有共享数据(3)有多条语句在操作共享数据。
问题:(1).会有相同数据出现的原因是因为原子性导致,CPU的一次执行是个原子性操作。(2).会有零票和负数票出现的原因是线程的随机性导致的。
3.解决数据安全问题:基本思想是让程序没有安全问题的环境。即把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行。可以采用同步代码块,将可能出现问题的代码包括起来。
同步代码块为:synchronized(锁对象){代码块}
锁对象:就是任意的一个对象,这个对象相当于一把锁,哪个线程先进来,就先持有这把锁,锁住,其他线程进不来,直到这个线程执行完,其它线程才进来。
注意:多个线程要共用一个锁对象,即需要把这个锁对象定义成静态成员变量,才能被所有线程共享。否则仍然无法锁住。同步代码块的锁对象属于互斥锁(所谓互斥锁,就是一次最多只能有一个线程持有这把锁)。
同步代码块的缺点:使用同步代码块耗费资源,效率不高。
加同步代码块的售票程序代码入下:
public class MyRunnable implements Runnable {
private String name;
private static int tickets = 100;//定义成共享数据
private static final Object obj=new Object();//定义共享锁对象
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
}
}
}
}
}
4.(1).同步代码块:synchronized(锁对象){代码块};
锁对象:是任意对象;
(2)同步方法: public synchronized void method();
锁对象:是this;
(3)静态同步方法: public static synchronized void method()
锁对象:当前类对应的字节码文件对象。例如:MyRunnable.class
代码如下:
public class MyRunnable1 implements Runnable{
private String name;
private static int tickets = 100;//定义成共享数据
int n=0;
private static final Object obj=new Object();//定义共享锁对象
@Override
public void run() {
while (true) {
if(n%2==0){
synchronized (MyRunnable1.class) {//如果else中调用的是方法,改为this
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
}
}
} else{
sellTickets();
}
n++;
}
}
private static synchronized void sellTickets() {
synchronized (obj){
if (tickets > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
}
}
}
}
5.Lock接口
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
void lock()获取锁; void unlock()释放锁。
ReentrantLock是Lock的一个子类,一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
代码如下:
public class MyLock extends Thread{
String name;
private static Lock lock=new ReentrantLock();
private static int tickets=100;
public MyLock(String name){
this.name=name;
}
@Override
public void run() {
while(true){
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + "正在售票第" + tickets-- + "张票");
}
lock.unlock();
}
}
}
6死锁现象:多个线程,互相持有对方的锁,而不释放,造成线程处于等待的现象。如果同步代码块有嵌套现象,有可能出现死锁现象。
举例: 中国人和美国人一起吃饭,中国人使用的筷子,美国人使用的刀和叉 ,中国人获取到了美国人的刀,美国人获取到了中国人的一根筷子。则两个人都无法吃饭,处在等待的状态。
代码如下:线程1得到锁对象objA,线程2得到锁对象objB,但是线程1要想继续执行下去,必须再次得到锁对象objB,而线程2要想继续执行下去必须得到锁对象objA,但是由于程序没有执行完,线程1不能释放锁objA,线程2不能释放锁objB,互相僵持,就是所谓的死锁现象。
public class SiSuoThread extends Thread {
public static final Object objA = new Object();
public static final Object objB = new Object();
boolean b;
public SiSuoThread(boolean b) {
this.b = b;
}
@Override
public void run() {
if (b) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objA) {
System.out.println("对象ObjA,true进来了");
synchronized (objB) {
System.out.println("对象ObjB,true进来了");
}
}
} else {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objB) {
System.out.println("对象ObjB,false进来了");
synchronized (objA) {
System.out.println("对象ObjA,false进来了");
}
}
}
}
}
public class SiSuoDemoTest {
public static void main(String[] args) {
SiSuoThread th1 = new SiSuoThread(true);
SiSuoThread th2 = new SiSuoThread(false);
th1.start();
th2.start();
}
}
7.不同种类线程之间的通信(等待唤醒机制)。
Object类中提供了三个方法: wait();等待
notify()唤醒单个线程
notifyAll()唤醒所有线程
等待唤醒机制的作用:就是生产一个,消费一个,交换输出。不是生产出一大堆,再去消费。案例解析:资源类:Student
设置学生数据:SetThread(生产者)
获取学生数据:GetThread(消费者)
测试类:StudentDemo
生产线程:如果没有资源,我就生产,有了资源我就等待,通知消费线程去消费掉,然后消费线程消费 完,我再生产;
消费线程:如果有了资源我就消费,没有资源通知生产线程生产资源。
代码如下:
//获取资源
ublic class GetThread extends Thread{
Student student;
public GetThread(Student student){
this.student=student;
}
@Override
public void run() {
//资源不存在,等待
while(true){
synchronized (student){
if(!student.flag){
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(student.name+"----"+student.age);
student.flag=false;
student.notify();
}
}
}
}
//生产线程
public class SetThread extends Thread{
Student student;
public SetThread(Student student){
this.student=student;
}//有参构造保证传的参数是一致的
int i=0;
@Override
public void run(){
while(true){
synchronized (student){
//判断资源是否存在
if(student.flag){//存在,等待
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不存在,生产资源
if(i%2==0){
student.name="张三";
student.age=23;
}else{
student.name="李四";
student.age=24;
}
//更改标记
student.flag=true;
student.notify();
i++;
}
}
}
}public class Student {
public String name;
public int age;
boolean flag;//标记有无资源,true有资源,false无资源
}
public class StudentDemo {
public static void main(String[] args) {
Student student=new Student();
SetThread setThread=new SetThread(student);
GetThread getThread=new GetThread(student);
setThread.start();
getThread.start();
}
}
Sleep()与wait()方法区别:
相同点:这两个方 法都能使线程处于阻塞状态;
区别:1.sleep()方法必须要一个时间;Wait()方法可以要时间,也可以不要时间;
2. .sleep()方法休眠后不释放锁;Wait()方法一旦等待就要立马释放锁,下次醒来也是从这里醒来。
(三)作业:(1)复制文件,每个线程复制一个文件,2个线程分别复制两个文件。文件1:初恋未满.mp3;文件2:大悲咒.mp3。
public class MyThread1 extends Thread{
String srcpath="初恋未满.mp3";
String aimpath="初恋未满copy.mp3";
FileInputStream in;
FileOutputStream out;
@Override
public void run() {
try {
in=new FileInputStream(srcpath);
out=new FileOutputStream(aimpath);
int len=0;
byte[] bytes=new byte[1024];
while ((len=in.read(bytes))!=-1) {
out.write(bytes,0,len);
out.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class MyThread1 extends Thread{
String srcpath="初恋未满.mp3";
String aimpath="初恋未满copy.mp3";
FileInputStream in;
FileOutputStream out;
@Override
public void run() {
try {
in=new FileInputStream(srcpath);
out=new FileOutputStream(aimpath);
int len=0;
byte[] bytes=new byte[1024];
while ((len=in.read(bytes))!=-1) {
out.write(bytes,0,len);
out.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread1 th2 = new MyThread1();
th1.start();
th2.start();
}
}
(2)3个 线程复制 同一个文件。
public class MyThread2 extends Thread {
long startpoint;
long endpoint;
RandomAccessFile srcfile;
RandomAccessFile aimfile;
public MyThread2(long startpoint, long endpoint, String srcpath, String aimpath) throws FileNotFoundException {
this.startpoint=startpoint;
this.endpoint=endpoint;
this.srcfile=new RandomAccessFile(srcpath,"rw");
this.aimfile=new RandomAccessFile(aimpath,"rw");
}
@Override
public void run() {
try {
aimfile.seek(startpoint);//确定文件开始指针
srcfile.seek(startpoint);
int len=0;
byte[] bytes=new byte[10];
while(startpoint<endpoint &&(len = srcfile.read(bytes))!=-1){
startpoint+=len;
aimfile.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
srcfile.close();
aimfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class MyTest2 {
public static void main(String[] args) throws FileNotFoundException {
String srcpath = "大悲咒.mp3";
String aimpath = "大悲咒copy2.mp3";
int threadNum=3;
File file = new File(srcpath);
long length = file.length();
long averagelen = length / threadNum;
for (int i = 0; i < threadNum; i++) {
long startpoint = i * averagelen;
long endpoint = (i + 1) * averagelen;
MyThread2 th=new MyThread2(startpoint,endpoint,srcpath,aimpath);
th.start();
}
}
}