进程:
- 是程序的一次执行过程,有生命周期。
- 系统在运行时会为每个进程分配不同的内存区域
线程:
- 进程可进一步细化为线程,是一个程序内部的执行的路径
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
分线程对象的创建线程:
方式一:继承Thread类
- 创建Thread类的子类
- 重写Thread类的run();将当前线程要执行的操作声明在run内()
- 实例化Thread类的子类(在main中)
- 通过Thread类的子类对象调用其start();
start()方法的作用
启动进程
调用当前线程的run()- 主线程与分线程会争相去输出
public class Main {
public static void main(String[] args) {
//3.实例化Thread类的子类
Number num=new Number();
//4.通过Thread类的子类对象调用其start();
num.start();
}
}
//1.创建Thread类的子类
class Number extends Thread{
//2.重写run
public void run() {
//方法体
}
}
说明:
- 分线程对象的创建和启动工作,是由主线完成的
- 一个线程只能够启动一次
- 分线程的执行顺序和代码先后出现的顺序无关,与CPU的资源调度有关
public class Main {
public static void main(String[] args) {
//3.实例化Thread类的子类
Number num=new Number();
//4.通过Thread类的子类对象调用其start();
num.start();
Number num2=new Number();
num2.start();
结果不一定先输出num的线程
}
}
//1.创建Thread类的子类
class Number extends Thread{
//2.重写run
public void run() {
sout(Thread.currentThread().getName()+":"+i)//输出当前线程的名字
}
}
方式二:实现Runnable接口的方式
- 提供Runnable接口的实现类
- 实现接口中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start();
public class Main {
public static void main(String[] args) {
//3.c创建类的对象
PrintNum print=new PrintNum();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的子类
//由于Runable里没有start的方法,所以不能通过print.start()来启动线程
Thread t=new Thread(print);
//5.通过Thread类的对象调用start()
t.start();
}
}
//1.实现Runnable接口的实现类
class PrintNum implements Runnable{
//2.重写run方法
public void run(){
for (int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
方式三:实现Callable接口
//例题:遍历100以内的偶数,并计算所有偶数的和
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
//
//
// 接收返回值
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
方式四: 使用线程池的方式
//创建并使用多线程的第四种方法:使用线程池
//使用线程池创建多线程的好处:
//1.降低了资源的消耗,使用完的线程可以被复用。
//2.提高了程序的响应速度。
//3.便于管理。
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(20);
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
问题:
为什么通过start()调用的的是Thread类中的run(),此run()怎么就表现为对Runnable实现类中的run()的调用了呢?
解答:
线程安全(对于重票和错票):
方式一:同步代码块
- 需要被同步的代码,即为操作共享数据的代码
要求:包裹共享数据的代码,不能多不能少。- 何为共享数据?多个线程的共同操作的数据。比如ticket。
- 同步监视器,俗称锁。任何一个类的对象,都可以充当同步监视器。
要求:多个线程必须共用同一个同步监视器。
public class Main {
public static void main(String[] args) {
PrintNum print=new PrintNum();
Thread t=new Thread(print);
Thread t1=new Thread(print);
Thread t2=new Thread(print);
t.start();
}
}
class PrintNum implements Runnable{
private int ticket=100;
Object obj=new Object();
public void run(){
while (true){
synchronized (obj) {//obj为监视器
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}
}
}
}
}
方式二:
如果操作共享数据的代码,完整的声明在方法中,则可以考虑将此方法直接声明为同步方法
- 说明:
1.同步方法也有同步监视器,只不过是默认的同步监视器,不能显式定义
2.对于非静态的同步方法,同步监视器是this
3.对于静态的同步方法,同步监视器是当前类本身
public class Main {
public static void main(String[] args) {
PrintNum t=new PrintNum();
PrintNum t1=new PrintNum();
PrintNum t2=new PrintNum();
t.start();
t1.start();
t2.start();
}
}
class PrintNum extends Thread{
private static int ticket=100;
Object obj=new Object();
public void run(){
for (int i=1;i<=100;i++){
show();
}
}
public static synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}
}
}
方式三:
/*
* 除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的Lock锁的方式
*
* 一、步骤:
* 1. 创建Lock实现类的对象:ReentrantLock (可重入锁) --->保证多个线程使用同一个Lock对象
* 2. 调用Lock的方法:lock()。调用此方法的线程就可以操作共享数据。其他线程等待
* 3. 在操作共享数据时,可以调用Lock的方法:unlock()。此方法一定要执行!
*
* 二、面试题:synchronized 和 Lock方式的异同?
* 相同点:解决线程的安全问题的两种不同方式。 synchronized在jdk1.0就有。 Lock在jdk5.0新增
* 不同点:① Lock通过显式的调用unlock()结束对共享数据的操作,更灵活!② 在线程通信中,Lock搭配Condition使用更灵活。
* 使用建议:参考Lock的api:
* Lock implementations provide more extensive locking
* operations than can be obtained using synchronized methods
* and statements
*/
2. 代码实现
public class WindowTest {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable {
private int ticket = 100;
//1.
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//2.
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
} finally {
//3.
lock.unlock();
}
}
}
}
对比两种方式:
- 相同点
都需要进行run()方法的重写,但不是同一个run方法
启动线程都需要调用Thread类中的start()方法- 对比:方式二要好于方式一
类可以实现多个接口,但是只能继承一个父类
实现的方式更方便来处理共享数据的情况(因为不用加static,生命周期不那么长)- 联系
Thread类实现Runnable接口,所以Thread也重写了Runnable中的run()方法
继承法操作同一个资源类的方法:
public class Main{
public static void main(String[] args) {
Customer customer=new Customer(1122,new Account());
Thread t1=new Thread(customer);
Thread t2=new Thread(customer);
t1.start();
t2.start();
}
}
class Customer extends Thread{
private int acc;
private Account account;
public Customer(int acc,Account account) {
this.acc = acc;
this.account=account;
}
@Override
public void run() {
for (int i=0;i<3;i++){
account.addBanlancr();
}
}
}
class Account{
private int balance=0;
public synchronized void addBanlancr(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance+=1000;
System.out.println(Thread.currentThread().getName()+"存了"+balance+"元");
}
}
测试线程中的常用方法:
- start():启动线程,调用线程中的run
- run():将线程要执行的操作声明在此方法
- currentThread(): 获取执行当前代码的线程
- getName() :获取当前线程的名字
- setName(): 设置当前线程的名字,特殊的给主线程设置名字:Thread.currentThread().setName
- sleep(long milisecond): 使得一旦执行此方法,当前线程就阻塞指明的毫秒数
- yield(): 每当执行此方法时,线程主动释放cpu的执行权
- join(): 在线程a中调用线程b的join();此时线程a进入阻塞状态,知道线程b执行结束以后,才结束线程a的阻塞状态,线程a继续执行(即a线程暂停,执行b线程,知道b线程执行完毕,继续执行a线程)
- isAlive(): 判断线程是否存活,返回的是Boolean类型