一、进程
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
进程是一个具有一定独立功能的程序,一个实体,每一个进程都有它自己的地址空间。
二、进程的状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程具有以下三种基本状态。
- 就绪状态
- 运行状态
- 阻塞状态
三、线程
线程实际上是进程中的一个独立的控制单元。是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)
PS:Java VM 启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
四、线程的两种实现方式
- 继承Thread类
- 实现Runnable接口
PS:优先使用第二种方式实现进程,因为第一种方式是继承类,如果有其它需求,就无法再继承;而第二种是实现接口,如果有其它需求,还可以再继承或实现。
代码演示:
package com.joe.thread;
import java.util.Date;
/**
* 多线程的实现方式: 1、继承Thread类 2、实现Runnable接口
* 两种方式的区别:
* 继承Thread:线程代码存放在Thread子类run方法中
* 实现Runnable:线程代码存放在接口的子类run方法中
* @author joe
*
*/
public class ThreadDemo {
public static void main(String[] args) {
// 使用自定义线程
// 第一种实现线程方法:继承Thread类
MyThread myThread = new MyThread();
myThread.start();// 启动线程并执行该线程的run方法
// myThread.run(); 仅仅是对象调用方法。而线程创建了,并没有运行。
for (int i = 0; i < 20; i++) {
System.out.println("main-" + i);
}
// 第二种实现线程的方法:实现Runnable接口
MyRunnable myRunnable = new MyRunnable();
// myRunnable.run();
Thread thread2 = new Thread(myRunnable);
thread2.start();
}
}
// 自定义线程类1:继承Thread类
// 步骤:
// 1、定义类继承Thread
// 2、复写Thread类中的run方法
// 3、调用线程的start方法,该方法有两个作用:启动线程;调用run方法
// 为什么要覆盖run方法呢?
// Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法
// 也就是说Thread类中的run方法,用于存储线程要运行的代码
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date() + "-" + i);
}
}
}
// 自定义的线程2:实现Runnable接口
// 步骤:
// 1、定义类实现Runnable接口
// 2、覆盖Runnable接口中的run方法
// 将线程要运行的代码存放在该run方法中
// 3、通过Thread类建立线程对象
// 4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
// 为什么要将Runnable接口的子类对象传递给Thread的构造函数
// 因为,自定义的run方法所属的对象是Runnable接口的子类对象
// 所以要让线程去指定指定对象的run方法,就必须明确该run方法所属的对象
// 5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyRunnale-" + i);
}
}
}
五、线程的操作方法
- Thread(Runnable target)
分配新的 Thread 对象。
- Thread(Runnable target, String name)
分配新的 Thread 对象。
- Thread(String name)
分配新的 Thread 对象。
- static Thread currentThread()
返回对当前正在执行的线程对象的引用。
- long getId()
返回该线程的标识符。
- String getName()
返回该线程的名称。
- void setName(String name)
改变线程名称,使之与参数 name 相同。
- boolean isAlive()
测试线程是否处于活动状态。
- static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
- static void sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
- void join()
等待该线程终止。
- void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。
- void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
- void interrupt()
中断线程。
- static boolean interrupted()
测试当前线程是否已经中断。
- void setPriority(int newPriority)
更改线程的优先级。
- int getPriority()
返回线程的优先级。
- boolean isDaemon()
测试该线程是否为守护线程。
- void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
- static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
六、线程同步
在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据。但也发现共享数据的同时,有时会出现安全问题。
出现问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多天语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式——同步。
同步的方法有两种:
- 同步代码块
synchronized(要同步的对象){
要同步的操作;
} - 同步方法
public synchronized void method(){
要同步的操作;
}
同步的准则:
当编写synchronized时,有几个简单的准则可以遵循,这些准则在便面死锁和性能危险的风险方面大有帮助:
- 使代码保持简短。把不随线程变化的预处理和后处理移出synchronized块
- 不要阻塞。如:InputStream.read()
- 在持有锁的时候,不要对其他对象调用方法
使用同步的前提:
- 必须要有两个或者两个以上的线程
- 必须是多个线程使用统一个锁
代码演示:
package com.joe.thread;
/**
* 多线程共享数据的安全问题,使用同步解决 1、同步代码块 2、同步方法 同步代码会带来性能降低的问题,提高数据的安全性
*
* @author joe
*
*/
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t1 = new Thread(my, "Joe");
Thread t2 = new Thread(my, "kk");
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
Object obj = new Object();// 同步的标记对象
@Override
public void run() {
// 同步代码块
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "正在吃饭...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃完了");
}
// doMethod();
}
/**
* 同步方法:同步的是当前对象(this)
*/
public synchronized void doMethod() {
System.out.println(Thread.currentThread().getName() + "正在吃饭...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃完了");
}
}
七、线程死锁
过多的同步有可能出现死锁,死锁的操作一般是在程序运行的时候才有可能出现。
代码演示:
package com.joe.thread;
public class DeadLockTest {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
class Test implements Runnable {
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
} else {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("els locka");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
八、wait方法和notify方法
代码演示:
package com.joe.thread;
/**
* wait(); notify(); notifyAll() 都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁
*
* 为什么这些操作线程的方法要定义Object类中呢?
* 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒
* ,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object中
*
*
* @author joe
*
*/
// 主方法
public class Demo03 {
public static void main(String[] args) {
Res r = new Res();
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 Res {
String name;
String sex;
boolean flag = false;
}
// 创建一个输入类实现Runnable接口,重写run方法
class Input implements Runnable {
private Res r;
Input(Res r) { // 通过传入对象的方法保证执行的与输出类是同一个对象
this.r = r;
}
public void run() {
int x = 0; // 定义一个数字,用来交替操作
while (true) { //利用while循环不断执行程序
synchronized (r) { // 锁是同一个对象r
if (r.flag)
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (x == 0) {
r.name = "Kaykay";
r.sex = "girl";
} else {
r.name = "琪琪";
r.sex = "女女";
}
x = (x + 1) % 2; // 模2,永远只出现0或1
r.flag = true;
r.notify();
}
}
}
}
// 创建一个输出类实现Runnable接口,重写run方法
class Output implements Runnable {
private Res r;
Output(Res r) { // 通过传入对象的方法保证执行的与输入类是同一个对象
this.r = r;
}
public void run() {
while (true) {
synchronized (r) { // 锁是同一个对象r
if (!r.flag)
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(r.name + "......" + r.sex);
r.flag = false;
r.notify();
}
}
}
}
代码优化后:
package com.joe.thread;
/**
* wait(); notify(); notifyAll() 都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁
*
* 为什么这些操作线程的方法要定义Object类中呢?
* 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒
* ,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object中
*
*
* @author joe
*
*/
// 主方法
public class Demo03 {
public static void main(String[] args) {
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
// 创建一个对象类
class Res {
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name, String sex) {
if (flag)
try {
this.wait();
} catch (Exception e) {
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if (!flag)
try {
this.wait();
} catch (Exception e) {
}
System.out.println(name + "......" + sex);
flag = false;
this.notify();
}
}
// 创建一个输入类实现Runnable接口,重写run方法
class Input implements Runnable {
private Res r;
Input(Res r) { // 通过传入对象的方法保证执行的与输出类是同一个对象
this.r = r;
}
public void run() {
int x = 0; // 定义一个数字,用来交替操作
while (true) { // 利用while循环不断执行程序
if (x == 0)
r.set("Kaykay", "girl");
else {
r.set("琪琪", "女女");
}
x = (x + 1) % 2; // 模2,永远只出现0或1
}
}
}
// 创建一个输出类实现Runnable接口,重写run方法
class Output implements Runnable {
private Res r;
Output(Res r) { // 通过传入对象的方法保证执行的与输入类是同一个对象
this.r = r;
}
public void run() {
while (true) {
r.out();
}
}
}
JDK1.5升级操作
package com.joe.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* JDK1.5中提供了多线程升级解决方案。将同步Synchronized替换成了显示Lock操作。将Object中的wait,notify,
* notifyAll替换了Condition对象。 该对象可以Lock锁,进行获取。
* 在此示例中,实现了本方只唤醒对方操作
*
* 代码思路:
* 1、Lock lock = new ReentrantLock(); //创建锁
* Condition c1 = new Condition(); //创建Condition
* Condition c2 = new Condition();
* 2、lock.lock(); //调用lock方法
* try{ c1.await;......;c2.signal; } //让本方等待,唤醒对方操作
* finally{ lock.unlock(); } //释放锁
* @author joe
*
*/
public class CopyOfProducerConsumerDemo {
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 Resource01 {
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock(); //创建锁
private Condition conditon_pro = lock.newCondition(); //创建第一个Condition
private Condition conditon_con = lock.newCondition(); //创建第二个Condition
public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag)
conditon_pro.await();
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "...生产者..."
+ this.name);
flag = true;
conditon_con.signal();
} finally {
lock.unlock();
}
}
public void out() throws InterruptedException {
lock.lock();
try {
while (!flag)
conditon_con.await();
System.out.println(Thread.currentThread().getName()
+ "...消费者......" + this.name);
flag = false;
conditon_pro.signal();
} finally {
lock.unlock();
}
}
}
class Producer01 implements Runnable {
private Resource01 res;
Producer01(Resource01 res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.set("+商品+");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer01 implements Runnable {
private Resource01 res;
Consumer01(Resource01 res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.out();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}