——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——
一、进程
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
进程是一个具有一定独立功能的程序,一个实体,每一个进程都有它自己的地址空间。
二、进程的状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程具有以下三种基本状态。
- 就绪状态
- 运行状态
阻塞状态
三、线程
线程实际上是进程中的一个独立的控制单元。是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)
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();
}
}
}
}