线程和进程的概念
- 进程
- 概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有他自己的内存空间和系统资源
- 多进程的意义:单进程计算机只能做一件事情,而我们现在的计算机都可以一边玩游戏,一边听音乐,我们常见的操作系统都是多进程操作系统,例如Windows,Linux等等
- 思考:对于单核计算机来说,游戏进程和音乐进程是同时进行吗?当然不是,因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率
- 线程
-
概念:每个进程内部又可以执行多个任务,而这每个任务我们就可以看成是一个线程,是程序使用CPU的基本单位。所以进程是拥有资源的基本单位,线程是CPU调度的基本单位
-
多线程的意义:多线程不是提高应用程序的执行速度,而是为了提高应用程序的使用率
我们程序在运行过程当中,都是在强CPU的时间片(执行权),如果是多线程的程序,那么在强到CPU的执行权的概率应该比单线程程序强到的概率要大,那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率。但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机
并行和并发的区别
吃饭吃到一半,电话来了,一直等吃完了以后才去接,这就说明不支持并发也不支持并行。
吃饭吃到一半,电话来了,停下来接电话,接完之后继续吃饭,说明支持并发(不一定是同时的)
吃饭吃到一半,电话来了,一边打电话一边吃饭,说明支持并行
- 并发
指应用能够交替执行不同的任务 - 并行
指应用能够同时执行不同的任务
多线程程序实现
由于线程是依赖进程而存在,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。但是java是不能直接调用系统功能的,所以我们没有办法直接实现多线程程序,但是java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由java去调用这样的东西,然后提供一些类供我们使用。我恩就可以实现多线程程序了
- 方法一:继承Thread类
例如:
package com.westmo3.demo3;
public class MyDemo1 {
public static void main(String[] args) {
System.out.println("主线程开始执行");
MyThread thread = new MyThread();
thread.start();//启动子线程
System.out.println("主线程执行完毕");
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("子线程执行了");
}
}
Thread类中基本方法的使用
public final String getName():获取线程名称
public final void setName(String name):设置线程名称
public static Thread currentThread():获取当前执行的线程
public final int getPriority() :获取线程的优先级(线程的默认优先级是5)
public final void setPriority(int newPriority):设置线程的优先级
public static void sleep(long millis) :线程休眠
public final void join() 意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行,注意事项: 在线程启动之后,在调用方法
public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
public final void stop():停止线程的运行
public void interrupt():中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
案例演示
package com.westmo3.demo3;
public class MyDemo1 {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始执行");
MyThread thread = new MyThread();
thread.start();//启动子线程
thread.sleep(2000);//让线程睡眠2s
MyThread myThread = new MyThread("1号线程");//利用有参构造该线程起个名字
MyThread myThread1 = new MyThread("2号线程");
MyThread myThread2 = new MyThread("3号线程");
myThread.start();
myThread.join();//可以让多个线程顺序执行
myThread1.start();
myThread1.join();
myThread2.start();
myThread2.join();
System.out.println("主线程执行完毕");
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String s) {
super(s);
}
@Override
public void run() {
//获取当前线程的线程名
//System.out.println(this.getName()+"-子线程执行了");
System.out.println(Thread.currentThread().getName()+"-子线程执行了");
}
}
- 方法二:实现Runnable接口
package com.westmo3.demo3;
public class MyDemo2 {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
Thread thread = new Thread(myThread1);
thread.start();
Thread thread1 = new Thread(myThread1);
thread1.start();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
- 方式三:实现Callable接口,相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
package com.westmo3.demo3;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyDemo3 {
public static void main(String[] args) {
thread thread = new thread();
FutureTask<Integer> task = new FutureTask<Integer>(thread);
Thread thread1 = new Thread(task);
thread1.start();
}
}
class thread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
return 200;
}
}
案例分析引出线程安全问题
案例:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
分析:根据题目我们知道,这三个窗口买票是同时进行的,我们可以用三个线程来表示这三个窗口
package com.westmo3.demo3;
public class MyDemo4 {
public static void main(String[] args) {
mythread th1 = new mythread();
mythread th2 = new mythread();
mythread th3 = new mythread();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class mythread extends Thread{
static int piao=100;//设置为共享变量,因为三个线程共同卖这100张
@Override
public void run() {
while(true){
if (piao>0) {
try {
Thread.sleep(10);//用线程睡眠来演示网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"正在出售"+(piao--)+"票");
}
}
}
}
从代码运行可以看出,出现了同票,0票甚至负票的情况,这些问题其实就是多线程环境下的数据安全问题。当其中一个线程抢得执行权执行时,当这个线程还没有执行完时,另一个线程又强到了执行权,这就是问题发生的本质。
解决方法:把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- 同步代码块方式
- 格式:
synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
死循环
需要同步的代码;
}
- 弊端:它虽然解决了多线程的安全问题,但当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
- 案例演示
package com.westmo3.demo3;
public class MyDemo {
public static void main(String[] args) {
mythread1 th1 = new mythread1();
mythread1 th2 = new mythread1();
mythread1 th3 = new mythread1();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class mythread1 extends Thread{
static int piao=100;//设置为共享变量,因为三个线程共同卖这100张
static Object obj=new Object();//三个线程的锁对象必须是同一个
@Override
public void run() {
while(true) {
synchronized (obj) {//线程进入同步代码块,就持有了锁,那么 其他两个线程,就在同步代码块外面等着
if (piao > 0) {
try {
Thread.sleep(20);//用线程睡眠来演示网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + "正在出售" + (piao--) + "票");
}
}
// 这个线程出了同步代码块,就 会把锁对象释放掉,这时候三个线程再次争抢时间片
}
}
}
- 同步方法:就是把关键字加到方法上
同步方法的锁对象:是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
package com.westmo3.demo3;
public class MydDemo5 {
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread th1 = new Thread(mythread);
Thread th2 = new Thread(mythread);
Thread th3 = new Thread(mythread);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Mythread implements Runnable{
static int piao=100;
int i=1;
@Override
public void run() {
while(true) {
if(i%2==0) {
Buypiao();//同步方法
}else {
Buypiao1();//静态同步方法
}
}
}
public synchronized void Buypiao1() {//同步方法的锁对象是this
if (piao>0) {
try {
Thread.sleep(20);//用线程睡眠来演示网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
}
}
public static synchronized void Buypiao() {//静态同步方法的锁对象是当前类的字节码对象
if (piao>0) {
try {
Thread.sleep(20);//用线程睡眠来演示网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
}
}
}
- Java中锁的知识
- java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果线程B不释放这个锁,那么线程A将永远等待下去
- java对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类和对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在,它只是用来用来帮助我们理解锁定实例方法和静态方法的区别
JDK1.5之后的Lock锁的使用
- 概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对线Lock - 使用:void lock()加锁,void unlock()释放锁
- 案例
package com.westmo3.demo3;
import java.util.concurrent.locks.ReentrantLock;
public class MyDemo5 {
public static void main(String[] args) {
MyThrad1 myThrad1 = new MyThrad1();
Thread th1 = new Thread(myThrad1);
Thread th2 = new Thread(myThrad1);
Thread th3 = new Thread(myThrad1);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class MyThrad1 implements Runnable{
static int piao=100;
static ReentrantLock reentrantLock=new ReentrantLock();
@Override
public void run() {
while(true){
reentrantLock.lock();
if(piao>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
}
reentrantLock.unlock();
}
}
}